SQL Server Enterpriseで実行されているベンダーアプリを使用しており、ほとんどの財務ドキュメント(注文、請求書など)を処理しているときに、ItemsテーブルでCOUNT
ステートメントを実行するというやや面倒な癖があります。
例えば。 SELECT COUNT('A') FROM [dbo].[Items] T0
通常はそれで問題ないと思いますが、600万件を超えるレコードがあり、それらすべてを数えるには400ミリ秒程度かかります。これは、全体の処理時間のかなりの部分を占める可能性があります。
テーブルにはすでに非常に狭い非クラスター化インデックス(tinyintとクラスター化キー)があり、SQLがテーブルスキャンを実行するときに使用しているため、その点でこれ以上改善できるとは思いません。
私が知っているいくつかの解決策がありますが、可能であれば避けたいものです。
COUNT_BIG(*)
を使用したインデックス付きビューこれをスピードアップする他のオプションはありますか?
ここに設定を示す要点があります: https://Gist.github.com/elvishfiend/5094f120b14f8ecfb325623edcb5f3eb
インデックス付きビューは、メンテナンスのオーバーヘッドが最も少ない最適に実装されている場合の最も高速なオプションの1つである必要があります。
詳細は 実行プランのインデックス付きビューのメンテナンス で説明しているように、変更は増分(デルタ)です(すべてのベーステーブルの更新で完全な再カウントは実行されません)。ただし、実行プランの差分更新部分に、(クエリと同様に)効率的なアクセス方法があることを確認する必要があります。
通常、_INSERT/UPDATE/DELETE
_実行プランから欠落しているインデックスを特定するのは非常に簡単です。おそらく、例示的な実行後(実際の)実行計画を質問に追加できます。
クエリテキストとインデックス付きビューの自動マッチングは、Enterprise Edition(および同等の機能)でのみ使用できます。他のエディションでは、WITH (NOEXPAND)
テーブルヒントを使用する必要があります。 Enterprise EditionでもNOEXPAND
を使用する 正当な理由 もあります。
デモコードについて:WITH (NOEXPAND)
を使用してヒントを指定してください。あなたが書いた方法では、NOEXPAND
はエイリアスとして解析されます。マテリアライズド(インデックス付き)ビューのみがNOEXPAND
ヒントを持つことができることにも注意してください。
ヒントを直接追加できない場合、これはプランガイドの優れた使用法になります。プランガイドを使用して、(明示的に名前を付けずに)インデックス付きビューに一致するクエリが実際にインデックス付きビューを使用するようにすることもできます。
マテリアライズド(インデックス付き)ビューにNOEXPAND
がない場合、SQL Serverはプランのコンパイルの開始時に常にビュー定義を拡張することに注意してください。 Enterprise Edition 可能(またはそうではない)各オプションのコストの評価に応じて、クエリをインデックス付きビューに(その一部に)一致させます。
関連するQ&A:
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万行をカウントできます。
手動で更新するid列を追加して、常に雨、夏、または冬の順に来るようにすることができます。
単一のテーブルがあり、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
_
更新された統計に依存しないと聞いていますが、よくわかりません。
_sp_spaceused employee
_更新された統計に依存します。
一度実行するジョブのようなものを作成し、最新の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
_条件がある場合にも適しています