4

いくつかの基本

2 つのテーブルがあり、1 つはユーザーを保持し、もう 1 つはログインのログを保持しています。ユーザー テーブルには 15000 人以上のユーザーが保持され、ログイン テーブルは成長しており、150000 件以上の投稿に達しています。データベースは SQL Server 上に構築されています (Express ではありません)。

ユーザーを管理するために、ObjectDatasource から入力するグリッドビュー (Devexpress の ASPxGridView) を取得しました。

ユーザーが行ったログインの数を要約するときに知っておくべき一般的な注意事項はありますか?

物事は奇妙に遅くなっています。

関連するテーブルを示す図を次に示します。 ここに画像の説明を入力

私はいくつかのことを試しました。

DbDataContext db = new DbDataContext();

// Using foregin key relationship
foreach (var proUser in db.tblPROUsers)
{
    var count = proUser.tblPROUserLogins.Count;
    //...
}

実行時間: 01:29.316 (1 分 29 秒)

// By storing a list in a local variable (I removed the FK relation)
var userLogins = db.tblPROUserLogins.ToList();
foreach (var proUser in db.tblPROUsers)
{
    var count = userLogins.Where(x => x.UserId.Equals(proUser.UserId)).Count();
    //...
}

実行時間: 01:18.410 (1 分 18 秒)

// By storing a dictionary in a local variable (I removed the FK relation)
var userLogins = db.tblPROUserLogins.ToDictionary(x => x.UserLoginId, x => x.UserId);
foreach (var proUser in db.tblPROUsers)
{
    var count = userLogins.Where(x => x.Value.Equals(proUser.UserId)).Count();
    //...
}

実行時間: 01:15.821 (1 分 15 秒)

最高のパフォーマンスを発揮するモデルは、実際には辞書です。ただし、そのような大量のデータを処理するときにこの種のコーディングに「悪い」ものがある場合も、それについて聞きたいオプションがあれば知っています。

ありがとう

================================================== ======

更新 BrokenGlass の例によるモデルを使用

// By storing a dictionary in a local variable (I removed the FK relation)
foreach (var proUser in db.tblPROUsers)
{
    var userId = proUser.UserId;
    var count = db.tblPROUserLogins.Count(x => x.UserId.Equals(userId));
    //...
}

実行時間: 02:01.135 (2 分 1 秒)

これに加えて、単純なクラスを格納するリストを作成しました

public class LoginCount
{
    public int UserId { get; set; }
    public int Count { get; set; }
}

そしてまとめ方では

var loginCount = new List<LoginCount>();

// This foreach loop takes approx 30 secs
foreach (var login in db.tblPROUserLogins)
{
    var userId = login.UserId;

    // Check if available
    var existing = loginCount.Where(x => x.UserId.Equals(userId)).FirstOrDefault();
    if (existing != null)
        existing.Count++;
    else
        loginCount.Add(new LoginCount{UserId = userId, Count = 1});
}

// Calling it
foreach (var proUser in tblProUser)
{
    var user = proUser;
    var userId = user.UserId;

    // Count logins
    var count = 0;
    var loginCounter = loginCount.Where(x => x.UserId.Equals(userId)).FirstOrDefault();
    if(loginCounter != null)
        count = loginCounter.Count;
    //...
}

実行時間: 00:36.841 (36 秒)

これまでの結論、linq での要約は遅いですが、私はそこに着きました!

4

3 に答える 3

3

おそらく、同じことを行う SQL クエリを作成して、アプリケーションとは独立して (SQL Server Management Studio で) 実行しようとすると便利です。何かのようなもの:

SELECT UserId, COUNT(UserLoginId)
FROM tblPROUserLogin
GROUP BY UserId

(注: これは を選択するだけUserIdです。 から他のフィールドtblPROUserが必要な場合は、この基本的なクエリの「上に」単純な JOIN が必要になります。)

{UserId, UserLoginId} に複合インデックスがあり、それがクエリ プランで使用されていることを確認してください。tblPROUserLogin両方のフィールドをインデックス内にその順序で配置することで、テーブルに触れずにクエリを実行できるようになります。

ここに画像の説明を入力

次に、ベンチマークを実行して、LINQ コードよりも大幅に優れた時間を取得できるかどうかを確認します。

  • はいの場合は、LINQ を「誘導」して同様のクエリを生成する方法を見つける必要があります。
  • もしそうでなければ、あなたはすでにかつてない速さです。

- - 編集 - -

次の LINQ スニペットは、上記のクエリと同等です。

var db = new UserLoginDataContext();

db.Log = Console.Out;

var result =
    from user_login in db.tblPROUserLogins
    group user_login by user_login.UserId into g
    select new { UserId = g.Key, Count = g.Count() };

foreach (var row in result) {
    int user_id = row.UserId;
    int count = row.Count;
    // ...
}

コンソールに次のテキストを出力します。

SELECT COUNT(*) AS [Count], [t0].[UserId]
FROM [dbo].[tblPROUserLogin] AS [t0]
GROUP BY [t0].[UserId]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

--- 編集 2 ---

だけUserIdでなく「全体」のユーザーを取得するには、次のようにします。

var db = new UserLoginDataContext();

db.Log = Console.Out;

var login_counts =
    from user_login in db.tblPROUserLogins
    group user_login by user_login.UserId into g
    select new { UserId = g.Key, Count = g.Count() };

var result =
    from user in db.tblPROUsers
    join login_count in login_counts on user.UserId equals login_count.UserId
    select new { User = user, Count = login_count.Count };

foreach (var row in result) {
    tblPROUser user = row.User;
    int count = row.Count;
    // ...
}

コンソール出力には、次のクエリが表示されます...

SELECT [t0].[UserId], [t0].[UserGuid], [t0].[CompanyId], [t0].[UserName], [t0].[UserPassword], [t2].[value] AS [Count]
FROM [dbo].[tblPROUser] AS [t0]
INNER JOIN (
    SELECT COUNT(*) AS [value], [t1].[UserId]
    FROM [dbo].[tblPROUserLogin] AS [t1]
    GROUP BY [t1].[UserId]
    ) AS [t2] ON [t0].[UserId] = [t2].[UserId]
-- Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.30319.1

...インデックスが正しい場合、これは非常に効率的です。

ここに画像の説明を入力

于 2011-09-25T11:29:34.290 に答える
1

2番目のケースは、メモリ内ではなくデータベース側でカウントできるようにドロップする場合、常に最速である必要があります。ToList()

var userId = proUser.UserId;
var count = db.tblPROUserLogins.Count(x => x.UserId == userId);

また、EF はオブジェクトのマッピング プロパティを処理できないため、最初にユーザー ID を「プレーンな」プリミティブ変数に入れる必要があります。

于 2011-09-24T22:05:40.017 に答える
1

申し訳ありませんが、私は通常のコンピューターを使用していないため、これをブラインドで行っています。いくつか質問があります

  • logins テーブルにユーザー ID のインデックスがありますか?
  • このページ用に特別に作成されたビューを試しましたか?
  • ユーザーを取得するためにページングを使用していますか、それともすべてのカウントを一度に取得しようとしていますか?
  • SQL プロファイラーを実行して、実際に送信される SQL を見ましたか?

このようなものはあなたのために働きますか?

var allOfIt = from c in db.tblProUsers 
        select new {
             User  = c, 
             Count = db.tblProUserLogins.Count(l => l.UserId == c.UserId)
        }
        .Skip(pageSize * pageNumber)
        .Take(pageSize) // page size
于 2011-09-25T10:53:08.597 に答える