web-dev-qa-db-ja.com

大きなテーブルでのCount(*)の高速化

SQL Server Enterpriseで実行されているベンダーアプリを使用しており、ほとんどの財務ドキュメント(注文、請求書など)を処理しているときに、ItemsテーブルでCOUNTステートメントを実行するというやや面倒な癖があります。

例えば。 SELECT COUNT('A') FROM [dbo].[Items] T0

通常はそれで問題ないと思いますが、600万件を超えるレコードがあり、それらすべてを数えるには400ミリ秒程度かかります。これは、全体の処理時間のかなりの部分を占める可能性があります。

テーブルにはすでに非常に狭い非クラスター化インデックス(tinyintとクラスター化キー)があり、SQLがテーブルスキャンを実行するときに使用しているため、その点でこれ以上改善できるとは思いません。

私が知っているいくつかの解決策がありますが、可能であれば避けたいものです。

これをスピードアップする他のオプションはありますか?

ここに設定を示す要点があります: https://Gist.github.com/elvishfiend/5094f120b14f8ecfb325623edcb5f3eb

8
Zac Faragher

インデックス付きビューは、メンテナンスのオーバーヘッドが最も少ない最適に実装されている場合の最も高速なオプションの1つである必要があります。

詳細は 実行プランのインデックス付きビューのメンテナンス で説明しているように、変更は増分(デルタ)です(すべてのベーステーブルの更新で完全な再カウントは実行されません)。ただし、実行プランの差分更新部分に、(クエリと同様に)効率的なアクセス方法があることを確認する必要があります。

通常、_INSERT/UPDATE/DELETE_実行プランから欠落しているインデックスを特定するのは非常に簡単です。おそらく、例示的な実行後(実際の)実行計画を質問に追加できます。

クエリテキストとインデックス付きビューの自動マッチングは、Enterprise Edition(および同等の機能)でのみ使用できます。他のエディションでは、WITH (NOEXPAND)テーブルヒントを使用する必要があります。 Enterprise EditionでもNOEXPANDを使用する 正当な理由 もあります。

デモコードについて:WITH (NOEXPAND)を使用してヒントを指定してください。あなたが書いた方法では、NOEXPANDはエイリアスとして解析されます。マテリアライズド(インデックス付き)ビューのみがNOEXPANDヒントを持つことができることにも注意してください。

ヒントを直接追加できない場合、これはプランガイドの優れた使用法になります。プランガイドを使用して、(明示的に名前を付けずに)インデックス付きビューに一致するクエリが実際にインデックス付きビューを使用するようにすることもできます。

マテリアライズド(インデックス付き)ビューにNOEXPANDがない場合、SQL Serverはプランのコンパイルの開始時に常にビュー定義を拡張することに注意してください。 Enterprise Edition 可能(またはそうではない)各オプションのコストの評価に応じて、クエリをインデックス付きビューに(その一部に)一致させます。

関連するQ&A:

14
Paul White 9

SQL Server 2012で立ち往生している場合は、クラスター化インデックスキーだけにインデックスを作成してみてください。 TINYINT列のインデックスより少し小さいかもしれません。インデックスにページ圧縮を追加することもできます。これによりクエリを高速化できますが、テーブル内のデータによって異なります。

SQL Server 2016にアップグレードできる場合は、テーブルに非クラスター化列ストアインデックスを作成できます。これにより、COUNT(*)クエリが非常に高速になり、DML操作のオーバーヘッドが低くなります。ここに簡単なデモがあります:

DROP TABLE IF EXISTS #Items;

CREATE TABLE #Items (
    CLUST_KEY BIGINT NOT NULL,
    SMALL_COLUMN TINYINT NOT NULL,
    FILLER VARCHAR(50) NOT NULL,
    PRIMARY KEY (CLUST_KEY)
);

INSERT INTO #Items WITH (TABLOCK)
SELECT
    ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    , 1
    , REPLICATE('Z', 50)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

CREATE INDEX NCI ON #Items (SMALL_COLUMN);

SET STATISTICS TIME ON;

-- CPU time = 312 ms,  elapsed time = 320 ms.
SELECT COUNT(*)
FROM #Items
OPTION (MAXDOP 1);


CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI ON #Items (SMALL_COLUMN);

-- CPU time = 0 ms,  elapsed time = 1 ms.
SELECT COUNT(*)
FROM #Items
OPTION (MAXDOP 1);

NCCIを使用すると、20ミリ秒未満で600万行をカウントできます。

6
Joe Obbish
  1. 手動で更新するid列を追加して、常に雨、夏、または冬の順に来るようにすることができます。

  2. 単一のテーブルがあり、where条件または結合がない場合

    _ SELECT o.NAME
    ,o.schema_id
    ,ddps.row_count
    FROM sys.indexes AS i
    INNER JOIN sys.objects AS o ON i.OBJECT_ID = o.OBJECT_ID
    INNER JOIN sys.dm_db_partition_stats AS ddps ON i.OBJECT_ID = ddps.OBJECT_ID
      AND i.index_id = ddps.index_id
    WHERE i.index_id < 2
      AND o.is_ms_shipped = 0
      AND o.NAME = 'even'
      AND o.schema_id = 1
    _

更新された統計に依存しないと聞いていますが、よくわかりません。

  1. _sp_spaceused employee_更新された統計に依存します。

  2. 一度実行するジョブのようなものを作成し、最新のIDとカウントを保存できます

    最新のIDとしてnullでないテーブルItemCountを作成します。最新のカウントintがnullではありません

Itemcountテーブルには常に1行のみが含まれ、インデックスは必要ありません

_insert into ItemCount (Latestid,LatestCount)
select top 1  itemid
,(select count(*) from [dbo].[Items])LatestCount
from [dbo].[Items]
order by itemid DESC
_

-ここでカウントロジックは独自のものです

クエリでカウントが必要なときはいつでも、これを行うことができます。

_declare @LatestID INT
declare @LatestCount int

select @LatestID=LatestID,@LatestCount=LatestCount 
from ItemCount ic 

declare @FreshCount int
declare @NewCount int
SELECT @FreshCount=COUNT(1)  FROM [dbo].[Items] it
where it.itemid>=@LatestID 

set @NewCount=@FreshCount+@LatestCount
_

ここ_[dbo].[Items]_のitemid列にインデックスを付ける必要があります

これは、count (*)クエリに_join and filter_条件がある場合にも適しています

1
KumarHarsh