6

次の問題があります。

バックグラウンド

私は、MVC3、EF4、および jquery を使用して、450 万レコードのテーブルにオートコンプリート セレクターを実装しようとしています。

これはテーブルです:

CREATE TABLE [dbo].[CONSTA] (
  [afpCUIT] nvarchar(11) COLLATE Modern_Spanish_CI_AS NOT NULL,
  [afpNombre] nvarchar(30) COLLATE Modern_Spanish_CI_AS NULL,
  [afpGanancias] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
  [afpIVA] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
  [afpMonot] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
  [afpIntSoc] varchar(1) COLLATE Modern_Spanish_CI_AS NULL,
  [afpEmpl] varchar(1) COLLATE Modern_Spanish_CI_AS NULL,
  [afpAct] varchar(2) COLLATE Modern_Spanish_CI_AS NULL,
  CONSTRAINT [CONSTA_pk] PRIMARY KEY CLUSTERED ([afpCUIT])
)
ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [CONSTA_Nombre_idx] ON [dbo].[CONSTA]
  ([afpNombre])
WITH (
  PAD_INDEX = OFF,
  DROP_EXISTING = OFF,
  STATISTICS_NORECOMPUTE = OFF,
  SORT_IN_TEMPDB = OFF,
  ONLINE = OFF,
  ALLOW_ROW_LOCKS = OFF,
  ALLOW_PAGE_LOCKS = OFF)
ON [PRIMARY]
GO

テーブルはかなり静的で (毎月のバッチ更新のみが必要です)、読み取り専用です。

誰かがレコード (54MB) をダウンロードしたい場合、これは URL です:

http://www.afip.gob.ar/genericos/cInscripcion/22102011.zip

レコードの説明は次のとおりです。

http://www.afip.gob.ar/genericos/cInscripcion/archivoCompleto.asp

アプリのコードは次のとおりです。

コントローラ:

public class AltaMasivaController : Controller
{
    //
    // GET: /AltaMasiva/

    public ActionResult Index()
    {
        return View();
    }

    public JsonResult GetUsers(string query)
    {
        CENT2Entities db = new CENT2Entities();
        bool isCUIT = true;

        for(int j = 0; j < query.Length; j++)
            if (! Char.IsDigit(query, j))
            {
                isCUIT = false;
                break;
            }

        if (isCUIT)
        {
            // nvarchar search
            var x = from u in db.CONSTA
                    where u.afpCUIT.StartsWith(query)
                    orderby u.afpNombre
                    select new { label = u.afpNombre.TrimEnd(), id = u.afpCUIT };

            return Json(x.Take(50), JsonRequestBehavior.AllowGet);
        }
        else
        {
            // nvarchar search
            var x = from u in db.CONSTA
                    where u.afpNombre.StartsWith(query)
                    orderby u.afpNombre
                    select new { label = u.afpNombre.TrimEnd(), id = u.afpCUIT };

            return Json(x.Take(50), JsonRequestBehavior.AllowGet);
        }
    } 
}

見る:

@{
    viewbag.title = "index";
}

<h2>index</h2>
@html.textbox("user", "", new { style="width: 400px;" })

<script type="text/javascript">

$("input#user").autocomplete(
{ 
    source: function (request, response) 
    { 
        // define a function to call your action (assuming usercontroller) 
        $.ajax(
        { 
            url: '/altamasiva/getusers', type: "post", datatype: "json", 

            // query will be the param used by your action method 
            data: { query: request.term }, 

            success: function(data){ 
                response( $.map(data, function (item){ return { label: item.label + " (" + item.id + ")", value: item.label, id: item.id }; })); 
            } 
        }) 
    }, 
    minlength: 1, // require at least one character from the user
});

</script>

そしていま:

問題

ご覧のとおり、クエリ文字列に数字のみが含まれている場合、コードはさまざまなパスをたどります。

コントローラー パラメーターのすべての文字が数字 (u.afpCUIT.StartsWith(query) の場合) の場合、クエリ オプティマイザーはクラスター化されたインデックス シークを実行し (実際に実行します)、最初に見つかった 50 行を返します。最初の「オートコンプリート」文字列 (通常は最大で 1 文字または 2 文字) が到着すると、クエリは非常に高速に実行されますが、文字列の長さが長くなると、パフォーマンスが著しく低下します (9 またはより多くの文字)。驚くべきことに、SQL Server サービスを「再起動」した後、最初の文字列に 10 文字が含まれていれば、パフォーマンスも優れていますが、「クエリ」文字列から文字を削除するとパフォーマンスが低下し、まったく逆になります。

なぜこうなった?

SQL サーバーは、最初の実行計画をコンパイルするときに、大規模な結果セット (またはその逆) で非常に高速に実行するように最適化します。結果セットを絞り込む (または拡張する) 後続のクエリには、別の実行計画が必要です ... しかし ... EF によって生成された SQL は、コンマド パラメーターを使用して、ステートメントの再コンパイルを (正確に) 回避します ...

以下を実行して、実行計画キャッシュを消去します。

db.ExecuteStoreCommand("DBCC FREEPROCCACHE");

パフォーマンスを優れた応答時間に復元します...しかし...すべてのデータベースのすべてのプランを強制終了するため、他のすべてのキャッシュされたプランのパフォーマンスが低下します(通常は正常に実行されます)。

EF sql ステートメントのプロファイリングを行った後、クエリ アナライザーで DBCC FREEPROCCACHE を実行してから、EF が生成する sql を生成しました。これにより、さまざまな実行プランが生成され、パラメーターの長さとは関係なく、すべて 250 ミリ秒の範囲で実行されました。

DBCC FREEPROCCACHE

exec sp_executesql N'SELECT TOP (50) 
[Project1].[C1] AS [C1], 
[Project1].[C2] AS [C2], 
[Project1].[afpCUIT] AS [afpCUIT]
FROM ( SELECT 
    [Extent1].[afpCUIT] AS [afpCUIT], 
    [Extent1].[afpNombre] AS [afpNombre], 
    1 AS [C1], 
    RTRIM([Extent1].[afpNombre]) AS [C2]
    FROM [dbo].[CONSTA] AS [Extent1]
    WHERE [Extent1].[afpCUIT] LIKE @p__linq__0 ESCAPE N''~''
)  AS [Project1]
ORDER BY [Project1].[afpNombre] ASC',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'2023291%'

質問

よりエレガントな代替手段はありますか

db.ExecuteStoreCommand("DBCC FREEPROCCACHE");

?

驚くべきことに、クエリの 2 番目のパス ( u.afpNombre.StartsWith(query) ) は同じ問題の影響を受けず、優れたパフォーマンスを発揮します。明らかに、文字列の長さが変わっても実行計画は変わりません...

古いバージョンの EF で ObjectContext パラメーターを見つけました。

System.Data.EntityClient.EntityCommand.EnablePlanCaching

しかし、EF4 でそれを見つけることができませんでした。グローバルな結果が同じになるかどうかはわかりません。

私はこの問題に本当に戸惑い、本当の問題がどこにあるのかわからない

インデックスのデザインが悪い?パーティションの欠如?SQL Server 2008 Express エディション? EFはSQLを生成しましたか? 運が悪い?

どんな助けでも素晴らしいでしょう。事前にサンクス!

4

2 に答える 2

1

SQL Server のキャッシュから単一のプランを削除する方法があります。ここで詳細に説明されています: http://sqlblog.com/blogs/kalen_delaney/archive/2007/09/29/geek-city-clearing-a-single-plan-from-cache.aspx

また、ストアド プロシージャを作成し、それを LINQ2Entities を使用する代わりに Entity Framework にマップすることもできます。このようにして、SQL 構文に特定の変更を加え、常に同じになるようにします。

于 2011-11-01T10:25:16.613 に答える
0

ご指摘のとおり、SQL Server は、大きな結果セットを持つ 1 つのパラメーター値に対して最適化されるように計画をコンパイルします。結果セットが絞り込まれると、クエリのパフォーマンスが低下します。

このシナリオでは、クエリで「オプション (再コンパイル)」ヒントを使用する必要があるため、受信した値ごとにクエリが再コンパイルされます。

エンティティ フレームワークでこれを行うのはそれほど簡単ではありません。クエリにオプション (再コンパイル) を含めるには、DbCommandInterceptor を作成する必要があります。もう 1 つのオプションは、SQL Server でプラン ガイドを作成して、"オプション (再コンパイル)" をクエリに追加することです。

ここで DbCommandInterceptor に関する情報を見つけることができます -テーブル値関数を呼び出すときにクエリ ヒントを追加する

プラン ガイドについては、次のようなものが必要です。

EXEC sp_create_plan_guide   
'planguidename',   
N'SELECT TOP (50) 
[Project1].[C1] AS [C1], 
[Project1].[C2] AS [C2], 
[Project1].[afpCUIT] AS [afpCUIT]
FROM ( SELECT 
    [Extent1].[afpCUIT] AS [afpCUIT], 
    [Extent1].[afpNombre] AS [afpNombre], 
    1 AS [C1], 
    RTRIM([Extent1].[afpNombre]) AS [C2]
    FROM [dbo].[CONSTA] AS [Extent1]
    WHERE [Extent1].[afpCUIT] LIKE @p__linq__0 ESCAPE N''~''
)  AS [Project1]
ORDER BY [Project1].[afpNombre] ASC',
'SQL',   
NULL,   
N'@p__linq__0 nvarchar(4000)',
N'OPTION (recompile)'
于 2017-05-24T06:47:15.307 に答える