選言で大きなセットを効率的にフィルタリングする


9

テーブルが1つあるとしましょう

CREATE TABLE Ticket (
    TicketId int NOT NULL,
    InsertDateTime datetime NOT NULL,
    SiteId int NOT NULL,
    StatusId tinyint NOT NULL,
    AssignedId int NULL,
    ReportedById int NOT NULL,
    CategoryId int NULL
);

この例TicketIdでは主キーです。

ユーザーがこのテーブルに対して「部分的にアドホック」なクエリを作成できるようにしたい。クエリのいくつかの部分が常に修正されるので、私は部分的に言います:

  1. クエリは常に範囲フィルターを実行します InsertDateTime
  2. クエリは常に ORDER BY InsertDateTime DESC
  3. クエリは結果をページングします

ユーザーは、オプションで他の列をフィルターに掛けることができます。フィルターは、なし、1つ、または多くでフィルターできます。そして、各列に対して、ユーザーは分離として適用される値のセットから選択できます。例えば:

SELECT
    TicketId
FROM (
    SELECT
        TicketId,
        ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
    FROM Ticket
    WHERE InsertDateTime >= '2013-01-01' AND InsertDateTime < '2013-02-01'
      AND StatusId IN (1,2,3)
      AND (CategoryId IN (10,11) OR CategoryId IS NULL)
    ) _
WHERE RowNum BETWEEN 1 AND 100;

ここで、テーブルに100,000,000行あると想定します。

私が思いつくことができる最高のものは、「オプション」の各列を含むカバリングインデックスです。

CREATE NONCLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime DESC
) INCLUDE (
    SiteId, StatusId, AssignedId, ReportedById, CategoryId
);

これにより、次のようなクエリプランが得られます。

  • 選択する
    • フィルタ
        • シーケンスプロジェクト(計算スカラー)
          • セグメント
            • インデックスシーク

なかなかいい感じです。コストの約80%〜90%は、理想的なIndex Seekオペレーションによるものです。

この種の検索を実装するためのより良い戦略はありますか?

「固定」部分の結果セットが100秒または1000秒になる場合があるため、オプションのフィルタリングをクライアントにオフロードする必要はありません。その場合、クライアントはソートとページングも担当しますが、これはクライアントにとって作業が多すぎる可能性があります。


サブクエリを一時テーブルまたはテーブル変数に配置して、そのように構築することは可能でしょうか?大きなテーブルでは、サブクエリに悩まされることがあります。インデックスをカバーすることは、今のところあなたを連れて行くだけです。
ヴァルキリー

@Valkyrieは信じられないほど非効率的です。また、このクエリのバリアント(さまざまなパラメーターとオプションのwhere句はさまざま)が1日に1秒間に数回実行され、平均で100ミリ秒未満で結果を返す必要があることも考慮してください。私たちはすでにこれを行っており、今のところ大丈夫です。スケーラビリティーのためにパフォーマンスを改善し続ける方法についてのアイデアを探しています。
ジョセフデイグル2013

保管スペースの使用についてどの程度気にしますか?
Jon Seigel

@JonSeigelそれはどの程度かによります...しかし、どんな提案も見たいです
ジョセフ・

2
そして、結果の2ページ目を取得するためのアプローチ/クエリは何ですか?RowNum BETWEEN 101 AND 200
ypercubeᵀᴹ

回答:


1

この特定の作業負荷がテーブルに対するクエリの大部分である場合は、次のことを検討してください。

ALTER TABLE Ticket ADD CONSTRAINT PK_Ticket PRIMARY KEY NONCLUSTERED (TicketId);

CREATE UNIQUE CLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime ASC
);

考慮事項:

  • datetime2を使用できますか(SQL 2008+、柔軟な精度)
  • InsertDateTimeは精度内で一意になります
  • 時間に制約がない場合、一意のSQLはint型の非表示の一意識別子列を追加します。これは、クラスタ化されていないすべてのインデックスに追加されるため、正しいクラスター化レコードを参照できます。

利点:

  • テーブルの最後に新しい行を追加します
  • オプションのフィルター列を2回(クラスター化で1回、インクルードのインデックスリーフで1回)書き込むのを防ぎます。
  • あなたの時間の大部分は、多かれ少なかれファイラーとのクラスターインデックスシークにあります。
  • 次に、最も人気のある列ペアに他の非クラスター化インデックスを追加します

1

私は過去にこの手法を使用しました。テーブルはそれほど大きくありませんでしたが、検索条件はより複雑でした。

これは短いバージョンです。

CREATE PROC usp_Search
    (
    @StartDate  Date,
    @EndDate    Date,
    @Sites      Varchar(30) = NULL,
    @Assigned   Int = NULL, --Assuming only value possible
    @StartRow   Int,
    @EndRow     Int
    )
AS
DECLARE @TblSites   TABLE (ID Int)
IF @Sites IS NOT NULL
BEGIN
    -- Split @Sites into table @TblSites
END
SELECT  TicketId
FROM    (
        SELECT  TicketId,
                ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
        FROM    Ticket
                LEFT JOIN @TblSites
                    Ticket.SiteID = @TblSites.ID
        WHERE   InsertDateTime >= @StartDate 
                AND InsertDateTime < @EndDate
                AND (
                    @Assigned IS NULL 
                    OR AssignedId = @Assigned 
                    )
        ) _
WHERE   RowNum BETWEEN @StartRow AND @EndRow;



-1

クライアントがほぼ同じ方法で何度もフィルタリングしている場合は、それらのクエリのインデックスを作成できます。

たとえば、クライアントがSiteIdとStatusIdでフィルタリングしている場合、追加のインデックスを作成できます。

CREATE NONCLUSTERED INDEX IX_Ticket_InsertDateTime_SiteId_StatusId ON Ticket     
(InsertDateTime DESC,
 SiteId [ASC/DESC],
 StatusId [ASC/DESC] ) 
 INCLUDE ( ... );

このように、「より一般的な」クエリのほとんどは高速に実行できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.