web-dev-qa-db-ja.com

データベースからデータを取得する最速の方法

C#とSql Server 2008を使用したASP.NETプロジェクトに取り組んでいます。

3つのテーブルがあります。

UsersDataFieldsDataField Values

各ユーザーは各データフィールドに特定の値を持ち、この値はDataFieldsValuesに保存されます。

次に、次のようなレポートを表示します。

enter image description here

オブジェクトUserおよびDataFieldを作成しました。 DataFieldオブジェクトには、特定のユーザーのフィールドの値を取得するメソッドstring GetValue(User user)があります。

次に、ユーザーのリストList<User> usersおよびDataFieldsのリストList<DataField> fieldsと私は次のことを行います。

string html = string.Empty;
html += "<table>";
html += "<tr><th>Username</th>";
foreach (DataField f in fields)
{
   html += "<th>" + f.Name + "</th>";
}
html += "</tr>"

foreach (User u in users)
{
   html += "<tr><td>" + u.Username + "</td>"
   foreach (DataField f in fields)
   {
      html += "<td>" + f.GetValue(u) + "</td>";
   }
   html += "</tr>"
}
Response.Write(html);

これは正常に機能しますが、extremely slowであり、20人のユーザーと10個のデータフィールドについて話しています。これを達成するためのパフォーマンスの面でより良い方法はありますか?

編集:クラス内の各パラメーターについて、次のメソッドを使用して値を取得します。

public static string GetDataFromDB(string query)
{
    string return_value = string.Empty;
    SqlConnection sql_conn;
    sql_conn = new SqlConnection(ConfigurationManager.ConnectionStrings["XXXX"].ToString());
    sql_conn.Open();
    SqlCommand com = new SqlCommand(query, sql_conn);
    //if (com.ExecuteScalar() != null)
    try
    {
        return_value = com.ExecuteScalar().ToString();
    }
    catch (Exception x)
    {
    }
    sql_conn.Close();
    return return_value;
} 

例えば:

public User(int _Id)
{
this.Id = _Id
this.Username = DBAccess.GetDataFromDB("select Username from Users where Id=" + this.Id)
 //...
}
25
enb081

ここに役立つ2つの提案があります。最初の提案は、パフォーマンスを大幅に改善するものです。 2番目の提案も役立ちますが、おそらくあなたの場合、アプリを高速化することはできません。

提案1

メソッドGetDataFromDB(string query)を頻繁に呼び出します。毎回新しいSqlConnectionとSqlCommandを作成するため、これは悪いことです。これには時間とリソースがかかります。また、ネットワーク遅延がある場合は、これに発信中の通話数が掛けられます。だから、それは悪い考えです。

このメソッドを1回呼び出して、Dictionary<int, string>のようなコレクションにデータを設定して、ユーザーIDキーからユーザー名の値をすばやく検索できるようにすることをお勧めします。

このような:

// In the DataField class, have this code.
// This method will query the database for all usernames and user ids and
// return a Dictionary<int, string> where the key is the Id and the value is the 
// username. Make this a global variable within the DataField class.
Dictionary<int, string> usernameDict = GetDataFromDB("select id, username from Users");

// Then in the GetValue(int userId) method, do this:
public string GetValue(int userId)
{
    // Add some error handling and whatnot. 
    // And a better name for this method is GetUsername(int userId)
    return this.usernameDict[userId];
}

提案2

ここでは少し改善できますが、StringBuilderクラスを使用することで改善できます。大幅なパフォーマンスの向上があります(概要は次のとおりです: http://support.Microsoft.com/kb/306822 )。

SringBuilder sb = new StringBuilder();
sb.Append("<table><tr><th>Username</th>");
foreach (DataField f in fields)
{
    sb.Append("<th>" + f.Name + "</th>");
}

// Then, when you need the string
string html = sb.ToString();

さらに明確にする必要がある場合はお知らせください。しかし、あなたが求めているのは非常に実行可能です。これを解決できます!

これら2つの簡単な変更を行うと、優れたパフォーマンスが得られます。保証します。

21
Chris Leyva

選択するデータベース設計の名前は Entity-Attribute-Value です。これは、パフォーマンスの問題でよく知られている設計です。 SQL Serverチームは、EAV設計に関するガイダンスのためのホワイトペーパーをリリースしました。 パフォーマンスとスケーラビリティのためのセマンティックデータモデリングのベストプラクティス を参照してください。

悲しいかな、あなたはすでに適切なデザインを持っていますが、今それについて何ができますか?重要なのは、dBへの無数の呼び出しを1回の呼び出しに減らし、1つのセット指向ステートメントを実行してデータを取得することです。ゲームの名前は Table Valued Parameters :です。

declare @users as UsersType;

insert into @users (UserId) values (7), (42), (89);

select ut.Id, 
  ut.Username, 
  df.Name as DataFieldName, 
  dfv.Value
from Users ut
join @users up on ut.Id = up.UserId
join DataFieldValues dfv on ut.Id = dfv.UserId
join DataFields df on dfv.DataFieldId = df.Id
order by ut.Id;

完全な例については、この SqlFiddle を参照してください。

厳密に言えば、 PIVOT 演算子を使用して、希望する形状(列名として転置されたデータフィールド名)の結果を取得することは可能ですが、そう。 PIVOT自体はパフォーマンスの泥沼ですが、目的の結果セットの動的な性質を追加すると、それを引き出すことは基本的に不可能です。ユーザーIDによる必要な順序により、相関する属性のセット間の完全な区切りが保証されるため、属性を1行1行で構成する従来の結果セットは、テーブルに解析するのは簡単です。

18
Remus Rusanu

ボンネットの下では、データベースに対して20 x 10 = 200のクエリを実行しているため、これは遅いです。正しい方法は、1ターンですべてをロードすることです。

データのロード方法に関する詳細を投稿する必要があります。 Entity Frameworkを使用している場合、Includeコマンドを使用したイーガーロードと呼ばれるものを使用する必要があります。

// Load all blogs and related posts
var blogs1 = context.Blogs
                      .Include(b => b.Posts)
                      .ToList();

いくつかのサンプルはここにあります: http://msdn.Microsoft.com/en-us/data/jj574232.aspx

編集:

.NET Frameworkが提供するツールを使用していないようです。最近では、あなたのような単純なシナリオでは、独自のデータベースアクセスを行う必要はありません。また、文字列HTMLを連結することは避けてください。

既存のASP.NETコントロールとEntity Frameworkを使用してアプリケーションを再設計することをお勧めします。

ここにあなたのためのステップバイステップの指示を持つサンプルがあります: http://www.codeproject.com/Articles/363040/An-Introduction-to-Entity-Framework-for-Absolute-B

14
Kaspars Ozols

Remus Rusanuが言ったように、PIVOTのパフォーマンスに関する限り、PIVOTリレーショナル演算子を使用して、必要な形式で必要なデータを取得できます。これは、テーブルのインデックス付けと可変性に依存することがわかりました。およびデータセットのサイズ。私たちが学ぶためにここにいるので、私は彼からPIVOTsの意見についてもっと聞くことに非常に興味があります。 PIVOT vs JOINS ここ で素晴らしい議論があります。

DataFieldsテーブルが静的なセットである場合、SQLを動的に生成することを心配する必要はなく、ストアドプロシージャを自分で構築できます。変動する場合は、動的SQLのパフォーマンスヒットを取得する必要があります (これに関する優れた記事) または別のアプローチを使用します。

さらにデータが必要でない限り、返されるセットを表示に必要な最小限に保つようにしてください。データベースがウェブサーバーと同じ物理サーバー上にない限り、すべてがネットワークを経由する必要があるため、オーバーヘッドを減らすのに良い方法です。

できるだけ少ない個別のデータ呼び出しを実行して、接続の確立と削除にかかる時間を短縮するようにしてください。

ループのコントロールが(おそらく関連している?)データセットに基づいている場合、ループ内のデータ呼び出しを常にダブルチェックする必要があります。

SQLを試しているときは、実行計画に精通してみてください。これは、クエリの実行速度が遅い理由を調べるのに役立ちます。詳細については、 これらのリソース を参照してください。

アプローチが何であれ、コードのボトルネックがどこにあるかを把握する必要があると判断します。これは、実行をステップ実行するのと同じくらい基本的なことが役立つため、問題のある場所を自分で確認できるため、特定することもできます自分のアプローチで起こりうる問題を解決し、適切な設計選択習慣を構築します。

Marc Gravelには、c#データの読み取りについて興味深い点がいくつかあります ここ 記事は少し古いですが、読む価値があります。

データのピボット(Sorry Remus ;-))指定したデータの例に基づいて、次のコードは必要なものを取得します。クエリ再帰:

--Test Data
DECLARE @Users AS TABLE ( Id int
                        , Username VARCHAR(50)
                        , Name VARCHAR(50)
                        , Email VARCHAR(50)
                        , [Role] INT --Avoid reserved words for column names.
                        , Active INT --If this is only ever going to be 0 or 1 it should be a bit.
                        );

DECLARE @DataFields AS TABLE ( Id int
                        , Name VARCHAR(50)
                        , [Type] INT --Avoid reserved words for column names.
                        );

DECLARE @DataFieldsValues AS TABLE ( Id int
                        , UserId int
                        , DataFieldId int
                        , Value VARCHAR(50)
                        );

INSERT INTO @users  ( Id
                    , Username
                    , Name
                    , Email
                    , [Role]
                    , Active) 
VALUES (1,'enb081','enb081','[email protected]',2,1),
       (2,'Mack','Mack','[email protected]',1,1),
       (3,'Bob','Bobby','[email protected]',1,0)


INSERT INTO @DataFields  
                    ( Id
                    , Name
                    , [Type]) 
VALUES (1,'DataField1',3),
       (2,'DataField2',1),
       (3,'DataField3',2),
       (4,'DataField4',0)

INSERT INTO @DataFieldsValues  
                    ( Id
                    , UserId
                    , DataFieldId
                    , Value) 
VALUES (1,1,1,'value11'),
       (2,1,2,'value12'),
       (3,1,3,'value13'),
       (4,1,4,'value14'),
       (5,2,1,'value21'),
       (6,2,2,'value22'),
       (7,2,3,'value23'),
       (8,2,4,'value24')

--Query
SELECT *
FROM
(   SELECT  ut.Username, 
            df.Name as DataFieldName, 
            dfv.Value
    FROM @Users ut
    INNER JOIN @DataFieldsValues dfv 
        ON ut.Id = dfv.UserId
    INNER JOIN @DataFields df 
        ON dfv.DataFieldId = df.Id) src
PIVOT
(   MIN(Value) FOR DataFieldName IN (DataField1, DataField2, DataField3, DataField4)) pvt

--Results
Username    DataField1  DataField2  DataField3  DataField4
enb081      value11     value12     value13     value14
Mack        value21     value22     value23     value24

覚えておくべき最も重要なことは、私たちが提案したものは、あなたが知らないあなたのサイトの要因によって変更される可能性があるので、自分で物事を試すことです。

6
Mack

ループごとにデータベースに接続していないことを確認してください。

ご覧のとおり、f.GetValue(u)部分は、データベースから取得した文字列値を返すメソッドです。

データをオブジェクトに一度だけ入れ、f.GetValue(u)がここで行っているのと同じことを行います。

5
evilom

どのようにデータベースにアクセスしていますか?たとえば、EFを使用している場合、プロファイラーでこれらのクエリから生成されたSQLを確認します。 foreachループで毎回接続しないでください。

サーバー側でもhtmlを作成しません。ページデータソースコントロールのオブジェクトを返すだけです。

5
antao

最悪の問題:データベースへの大量の往復。値を取得するたびに、ネットワークを介してリクエストが送信され、結果を待ちます。

最初にユーザーリストをコードに含める必要がある場合は、次のことを確認してください。

  1. ユーザーリスト内のすべての情報を1回のdb呼び出しで取得します。ユーザーIDのセットがある場合は、テーブル値パラメーターを使用して送信できます。
  2. 上記にフィールド値が含まれていない場合は、ユーザーIDのリストとフィールドIDのリストを2つのテーブル値パラメーターで送信して、すべてを一度に取得します。

それは大きな違いを生むはずです。これら2つの特定のクエリを使用すると、ネットワークノイズを排除でき、必要に応じてインデックスの改善に集中できます。

もう1つの利点は、文字列を連結することです。最初のステップは、StringBuilderに置き換えることです。次のステップは、出力ストリームに直接書き込むことです。そのため、すべてのデータをメモリに保持する必要はありませんが、必要になることはほとんどありません。また、データが多すぎるためにこれを行うと、とにかくそれを処理するブラウザで問題が発生します。

追伸OPシナリオではありませんが、一括処理が必要な場合は、代わりに一括エクスポートが必要です。 http://technet.Microsoft.com/en-us/library/ms175937.aspx

4
eglasius

テーブルのプライマリキーフィールドにIndexedを使用し、コードビハインドでは文字列ビルダーを使用します。

速い...使用

  1. ストアドプロシージャ
  2. リーダーを使用する

    SqlDataReader dbReader = mySqlCommand.ExecuteReader();
    
    //if reader has row values
    if (dbReader.HasRows) // while(xxx) for more rows return
    {
         //READ DATA
    }
    
  3. パーティションに行く必要がある場合は、適切なインデックスを作成してください...

  4. SELECT NOLOCKの使用とHINTが機能する

クエリヒント(Transact-SQL) http://technet.Microsoft.com/en-us/library/ms181714.aspx

ロックのヒント http://technet.Microsoft.com/en-us/library/aa213026(v = sql.80).aspx

LINQを使用するのは、ストアドプロシージャを呼び出す場合だけです。

LINQ to SQLを検索

しかし、私は古い学校です....

Entity Framework 1.0は、学校でのプロジェクトを行うときに優れているため、このEntity Frameworkを削除します...

しかし、計算インスタンスとして非常に高価です...

メモリ内のすべてを読んで何か??? SQLを支払うのはなぜですか? JSONファイル構造を使用します。..

2
Valentin Petkov