5

何十億もの行を含むDBがあります。ユーザーからパラメータの数を受け取り、それらのパラメータでDBをカットする関数を作成しました。これは、小さな DB (30000 行) ではうまく機能しますが、大きな DB でこの関数を使用しようとすると、TIMEOUTEXCEPTION from SQLSERVER.

これが私のコードです:

public static IQueryable<LogViewer.EF.InternetEF.Log> ExecuteInternetGetLogsQuery(FilterCriteria p_Criteria, ref GridView p_Datagrid)
{
    IQueryable<LogViewer.EF.InternetEF.Log> internetQuery = null;

    using (InternetDBConnectionString context = new InternetDBConnectionString())
    {
        internetQuery = context.Logs;
        if ((p_Criteria.DateTo != null && p_Criteria.DateFrom != null))
        {
            internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom && c.Timestamp < p_Criteria.DateTo);
        }
        else if (p_Criteria.DateFrom != null && p_Criteria.DateFrom > DateTime.MinValue)
        {
            internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom);
        }
        else if (p_Criteria.DateTo != null && p_Criteria.DateTo > DateTime.MinValue)
        {
            internetQuery = internetQuery.Where(c => c.Timestamp < p_Criteria.DateTo);
        }
        if (!string.IsNullOrEmpty(p_Criteria.FreeText))
        {
            internetQuery = internetQuery.Where(c => c.FormattedMessage.Contains(p_Criteria.FreeText));
        }

        if (p_Criteria.Titles.Count > 0)
        {
            internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Titles.Contains(c.Title)).AsQueryable();
        }
        if (p_Criteria.MachineNames.Count > 0)
        {
            internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.MachineNames.Contains(c.MachineName)).AsQueryable();
        }
        if (p_Criteria.Severities.Count > 0)
        {
            internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Severities.Contains(c.Severity)).AsQueryable();
        }
        internetQuery= internetQuery.OrderByDescending(c=>c.LogID);
        if (internetQuery.Count() > p_Criteria.TopValue)
        {
            internetQuery = internetQuery.Take(p_Criteria.TopValue);
        }
        p_Datagrid.DataSource = internetQuery;
        p_Datagrid.DataBind();
        return internetQuery;

    }  
}

SQL のバージョンは 2005 ですp_Datagrid.DataBind();。行で例外が発生しました。

何か提案はありますか?ありがとう

4

6 に答える 6

3

私が見ることができるのは、次のオプションがあることです。

  • タイムアウトを増やします (Bad idé は問題を将来に移すだけです)
  • linq クエリを実行する代わりに。ストアプロシージャによるデータの取得
  • グリッドページを作成します。したがって、ターゲット ページのデータを取得するだけです。
  • クエリプランを見て、whereステートメントを実行している列にインデックスを作成できるかどうかを確認してくださいorder by
  • なぜデータグリッドに数十億の行が必要なのですか? 要件は何ですか?たぶん、top 1000またはを表示できますtop 10000。それぞれのユーザーからは、10億行のグリッドを見ることの利点がわかりません。

それは私の頭の上だけでした。

編集

そして、もし私がこの関数を持っているなら、私はコードのこのセクションを見始めます:

if (p_Criteria.Titles.Count > 0)
{
     internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Titles.Contains(c.Title)).AsQueryable();
}
if (p_Criteria.MachineNames.Count > 0)
{
      internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.MachineNames.Contains(c.MachineName)).AsQueryable();
}
if (p_Criteria.Severities.Count > 0)
{
      internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Severities.Contains(c.Severity)).AsQueryable();
}

これにより、実際に結果の IEnumerable が作成さwhereれ、データベース呼び出しでメモリ内ステートメントが実行されます。関連するテーブルを呼び出すと、データベースが呼び出されるため、これを行う際にも問題が発生する可能性があります。たぶん、行をフェッチしてからcontains、のIDで実行できますIQueryableIQueryableこれを行うときにおむつを持つことのすべての長所。

于 2012-04-17T12:13:43.343 に答える
2

一般に、このような「スイス アーミー ナイフ」仕様または基準パターンは、クライアント/ユーザーが指定できるフィルターの組み合わせの順列が多数あるため、最適化 (つまり、SQL レベルでのインデックス) が困難です。したがって、ユーザーに少なくとも 1 つの必須基準を指定するように強制することができれば、たとえば、日付範囲を必須にして 1 か月以内にすることで、行数を大幅に減らすことができれば、そこから始めます。インデックス作成を検討するときに開始します。

行数が多くなる可能性があるためp_Criteria.TopValue、行を制限するために使用される の値が常に存在し、適切な数値であることを主張または検証しますTake(1000)。このしきい値に達した場合、検索範囲を狭めるようにユーザーにいつでも警告できます。

Titles主な問題は、これまでのクエリを実体化する、MachineNamesおよびSeverities各呼び出しのフィルタリングである可能性が高いためAsEnumerable()、SQL ではなくメモリでこれら 3 つのフィルターを評価し、多数のレコードを使用する可能性があります。EF の最近のバージョンはすべて、フォームの述語をWhere(c => IEnumerable<X>.Contains(c.Column))SQLに変換できますWHERE c.Column IN (X1, X2, X3)

つまり、これら 3 つのフィルターの を削除するAsEnumerable()必要があります (その後、 に戻す必要はありませんAsQueryable())。

    if (p_Criteria.Titles.Any())
    {
        internetQuery = internetQuery
            .Where(c => p_Criteria.Titles.Contains(c.Title));
    }
    if (p_Criteria.MachineNamesAny())
    {
        internetQuery = internetQuery
            .Where(c => p_Criteria.MachineNames.Contains(c.MachineName));
    }
    if (p_Criteria.Severities.Any())
    {
        internetQuery = internetQuery
            .Where(c => p_Criteria.Severities.Contains(c.Severity));
    }

Takeチェックで実行することによる、チェックの別の問題は.Count()、クエリを実体化していることです (まだ実行していない場合)。代わりに、Take()直接実行する必要があります。行数を超えたかどうかを確認する必要はありません。行が LESS 未満の場合、存在p_Criteria.TopValueする行と同じ数の行が返されます。つまり、if チェックを削除して、そのままにしておきます。

internetQuery = internetQuery.Take(p_Criteria.TopValue);

パフォーマンス上の理由から私が検討するもう 1 つのことは、FreeText 文字列チェックを変更してStartsWithContains. SQL データベースの char 列のインデックス作成は、文字列の先頭でのみ有効です。ワイルドカードが必要ない場合、これは明らかに OP のコードとは異なりますが、列%filter%でインデックスを使用できるようになります。FreeText

if (!string.IsNullOrEmpty(p_Criteria.FreeText))
{
    internetQuery = internetQuery
        .Where(c => c.FormattedMessage.StartsWith(p_Criteria.FreeText));
}

ちょっとした問題で、データベースのパフォーマンスには影響しませんが、日付フィルタリングのブランチの数を次のように減らすことができます。

if (p_Criteria.DateFrom != null && p_Criteria.DateFrom > DateTime.MinValue)
{
    internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom);
}
if (p_Criteria.DateTo != null && p_Criteria.DateTo > DateTime.MinValue)
{
    internetQuery = internetQuery.Where(c => c.Timestamp < p_Criteria.DateTo);
}

命名基準の観点から、Object/DbContext の名前も から に変更し*ConnectionStringます*Context

于 2012-04-17T12:17:30.110 に答える
1

解決策を1週間検索した後、この投稿を見つけました。これは、10 億行を超えるインデックス付き DB でうまく機能します。これが私のコードソリューションです:

public static IQueryable<LogViewer.EF.InternetEF.Log> ExecuteInternetGetLogsQuery(FilterCriteria p_Criteria, ref GridView p_Datagrid)
        {

            
            IQueryable<LogViewer.EF.InternetEF.Log> internetQuery = null;
            List<LogViewer.EF.InternetEF.Log> executedList = null;
            using (InternetDBConnectionString context = new InternetDBConnectionString())
            {
                internetQuery = context.Logs;
                if ((p_Criteria.DateTo != null && p_Criteria.DateFrom != null))
                {
                    internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom.Value && c.Timestamp < p_Criteria.DateTo.Value);
                }
                else if (p_Criteria.DateFrom != null && p_Criteria.DateFrom > DateTime.MinValue)
                {
                    internetQuery = internetQuery.Where(c => c.Timestamp >= p_Criteria.DateFrom);
                }
                else if (p_Criteria.DateTo != null && p_Criteria.DateTo > DateTime.MinValue)
                {
                    internetQuery = internetQuery.Where(c => c.Timestamp < p_Criteria.DateTo);
                }
                if (!string.IsNullOrEmpty(p_Criteria.FreeText))
                {
                    internetQuery = internetQuery.Where(c => c.FormattedMessage.Contains(p_Criteria.FreeText));
                }

                
                if (p_Criteria.Titles.Count > 0)
                {
                    internetQuery = internetQuery.Where(BuildOrExpression<LogViewer.EF.InternetEF.Log, string>(p => p.Title, p_Criteria.Titles));
                }
                if (p_Criteria.MachineNames.Count > 0)
                {
                    internetQuery = internetQuery.Where(BuildOrExpression<LogViewer.EF.InternetEF.Log, string>(p => p.MachineName, p_Criteria.MachineNames));
                }
                if (p_Criteria.Severities.Count > 0)
                {
                    internetQuery = internetQuery.Where(BuildOrExpression<LogViewer.EF.InternetEF.Log, string>(p => p.Severity, p_Criteria.Severities));
                }


                internetQuery = internetQuery.Take(p_Criteria.TopValue);
                executedList = internetQuery.ToList<LogViewer.EF.InternetEF.Log>();
                executedList = executedList.OrderByDescending(c => c.LogID).ToList<LogViewer.EF.InternetEF.Log>(); ;

                p_Datagrid.DataSource = executedList;

                p_Datagrid.DataBind();


                return internetQuery;

            }
        }



public static Expression<Func<TElement, bool>> BuildOrExpression<TElement, TValue>(
        Expression<Func<TElement, TValue>> valueSelector,
        IEnumerable<TValue> values )
        {
            if (null == valueSelector)
                throw new ArgumentNullException("valueSelector");

            if (null == values)
                throw new ArgumentNullException("values");

            ParameterExpression p = valueSelector.Parameters.Single();

            if (!values.Any())
                return e => false;

            var equals = values.Select(value =>
                (Expression)Expression.Equal(
                     valueSelector.Body,
                     Expression.Constant(
                         value,
                         typeof(TValue)
                     )
                )
            );

            var body = equals.Aggregate<Expression>(
                     (accumulate, equal) => Expression.Or(accumulate, equal)
             );

            return Expression.Lambda<Func<TElement, bool>>(body, p);
        }

これが私たちのコミュニティに役立つことを願っています ありがとう

于 2012-04-22T12:09:56.070 に答える
1

具体的なスキーマが利用できないため、次のことを試すことができます。

  1. すべてのフィルター基準を使用してストアド プロシージャを記述し、コードからパラメーターを送信します。次に、コードからストアド プロシージャを実行し、タイムアウトが発生するかどうかを確認します。EntityフレームワークからSPを呼び出す方法を確認するには、これを読んでください

  2. ステップ 1 でうまくいかない場合は、テーブルの設計を見直して、インデックスや追加のフィルターを追加することをお勧めします。SQL Server データベースのインデックス作成方法に関するガイドラインを確認するには、これをお読みください

  3. アーカイブされた DB 行を保持するために、テーブルの「シャドウ」コピーを作成することもできます。アーカイブ済みとは、現時点では役に立たないが、完全に削除できない行を意味します。

編集:すべての行をフェッチする代わりに、ページングされたグリッドを持つことについて@Arionに同意します。

于 2012-04-17T12:13:51.053 に答える
0

問題は、すべての結果をデータ グリッドにロードしようとしていることです。本当に必要ですか?ページングのようなものを使用して、最初の 100 行のみを読み取り、残りをオンデマンドでのみ読み取ることはできますか?

于 2012-04-17T12:18:54.447 に答える
0
internetQuery = internetQuery.AsEnumerable().Where(c => p_Criteria.Severities.Contains(c.Severity))

EF は、結合としてこのようなクエリを実行するのに十分スマートですか、それとも SELECT N + 1 の問題を引き起こしますか?

それ以外の場合は、何かのインデックスが不足している可能性があります。

最初に生成された SQL を確認します。

于 2012-04-17T12:27:32.687 に答える