順次GUIDと非順次GUIDを比較する this 質問を尋ねた後、1)のINSERTパフォーマンスをGUIDプライマリキーをnewsequentialid()
、および2)identity(1,1)
で順次初期化されたINT主キーを持つテーブル。整数の幅が小さいため、後者が最も高速になると思います。また、順次GUIDよりも順次整数を生成する方が簡単なようです。しかし、驚いたことに、整数キーを使用したテーブルでのINSERTは、シーケンシャルGUIDテーブルよりも大幅に低速でした。
これは、テスト実行の平均時間使用量(ms)を示しています。
NEWSEQUENTIALID() 1977
IDENTITY() 2223
誰かがこれを説明できますか?
次の実験が使用されました:
SET NOCOUNT ON
CREATE TABLE TestGuid2 (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))
CREATE TABLE TestInt (Id Int NOT NULL identity(1,1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))
DECLARE @BatchCounter INT = 1
DECLARE @Numrows INT = 100000
WHILE (@BatchCounter <= 20)
BEGIN
BEGIN TRAN
DECLARE @LocalCounter INT = 0
WHILE (@LocalCounter <= @NumRows)
BEGIN
INSERT TestGuid2 (SomeDate,batchNumber) VALUES (GETDATE(),@BatchCounter)
SET @LocalCounter +=1
END
SET @LocalCounter = 0
WHILE (@LocalCounter <= @NumRows)
BEGIN
INSERT TestInt (SomeDate,batchNumber) VALUES (GETDATE(),@BatchCounter)
SET @LocalCounter +=1
END
SET @BatchCounter +=1
COMMIT
END
DBCC showcontig ('TestGuid2') WITH tableresults
DBCC showcontig ('TestInt') WITH tableresults
SELECT batchNumber,DATEDIFF(ms,MIN(SomeDate),MAX(SomeDate)) AS [NEWSEQUENTIALID()]
FROM TestGuid2
GROUP BY batchNumber
SELECT batchNumber,DATEDIFF(ms,MIN(SomeDate),MAX(SomeDate)) AS [IDENTITY()]
FROM TestInt
GROUP BY batchNumber
DROP TABLE TestGuid2
DROP TABLE TestInt
UPDATE:次のPhil Sandler、Mitch Wheat、Martinの例のように、TEMPテーブルに基づいて挿入を実行するようにスクリプトを変更します。 IDENTITYが本来よりも速いことがわかります。しかし、これは行を挿入する従来の方法ではありません。最初に実験がうまくいかなかった理由がまだわかりません。元の例からGETDATE()を省略しても、IDENTITY()の方がずっと低速です。したがって、IDENTITY()をNEWSEQUENTIALID()よりも優れたものにする唯一の方法は、一時テーブルに挿入する行を準備し、この一時テーブルを使用してバッチ挿入として多くの挿入を実行することです。全体として、現象の説明が見つかったとは思いません。IDENTITY()は、ほとんどの実際的な使用法ではまだ遅いようです。誰かがこれを説明できますか?
@Phil Sandlerのコードを変更して、GETDATE()の呼び出しの影響を取り除き(ハードウェアの影響/割り込みが発生する可能性がありますか??)、行を同じ長さにしました。
[SQL Server 2000以降、タイミングの問題と高解像度タイマーに関連する記事がいくつかあるので、その影響を最小限に抑えたいと思いました。]
データとログファイルの両方が必要なサイズを超える単純な復旧モデルでのタイミング(秒単位)は次のとおりです(以下の正確なコードに基づく新しい結果で更新)
Identity(s) Guid(s)
--------- -----
2.876 4.060
2.570 4.116
2.513 3.786
2.517 4.173
2.410 3.610
2.566 3.726
2.376 3.740
2.333 3.833
2.416 3.700
2.413 3.603
2.910 4.126
2.403 3.973
2.423 3.653
-----------------------
Avg 2.650 3.857
StdDev 0.227 0.204
使用されるコード:
SET NOCOUNT ON
CREATE TABLE TestGuid2 (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(88))
CREATE TABLE TestInt (Id Int NOT NULL identity(1,1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))
DECLARE @Numrows INT = 1000000
CREATE TABLE #temp (Id int NOT NULL Identity(1,1) PRIMARY KEY, rowNum int, adate datetime)
DECLARE @LocalCounter INT = 0
--put rows into temp table
WHILE (@LocalCounter < @NumRows)
BEGIN
INSERT INTO #temp(rowNum, adate) VALUES (@LocalCounter, GETDATE())
SET @LocalCounter += 1
END
--Do inserts using GUIDs
DECLARE @GUIDTimeStart DateTime = GETDATE()
INSERT INTO TestGuid2 (SomeDate, batchNumber)
SELECT adate, rowNum FROM #temp
DECLARE @GUIDTimeEnd DateTime = GETDATE()
--Do inserts using IDENTITY
DECLARE @IdTimeStart DateTime = GETDATE()
INSERT INTO TestInt (SomeDate, batchNumber)
SELECT adate, rowNum FROM #temp
DECLARE @IdTimeEnd DateTime = GETDATE()
SELECT DATEDIFF(ms, @IdTimeStart, @IdTimeEnd) AS IdTime, DATEDIFF(ms, @GUIDTimeStart, @GUIDTimeEnd) AS GuidTime
DROP TABLE TestGuid2
DROP TABLE TestInt
DROP TABLE #temp
GO
@Martinの調査を読んだ後、どちらの場合も、推奨されるTOP(@num)で再実行しました。
...
--Do inserts using GUIDs
DECLARE @num INT = 2147483647;
DECLARE @GUIDTimeStart DATETIME = GETDATE();
INSERT INTO TestGuid2 (SomeDate, batchNumber)
SELECT TOP(@num) adate, rowNum FROM #temp;
DECLARE @GUIDTimeEnd DATETIME = GETDATE();
--Do inserts using IDENTITY
DECLARE @IdTimeStart DateTime = GETDATE()
INSERT INTO TestInt (SomeDate, batchNumber)
SELECT TOP(@num) adate, rowNum FROM #temp;
DECLARE @IdTimeEnd DateTime = GETDATE()
...
そしてここにタイミング結果があります:
Identity(s) Guid(s)
--------- -----
2.436 2.656
2.940 2.716
2.506 2.633
2.380 2.643
2.476 2.656
2.846 2.670
2.940 2.913
2.453 2.653
2.446 2.616
2.986 2.683
2.406 2.640
2.460 2.650
2.416 2.720
-----------------------
Avg 2.426 2.688
StdDev 0.010 0.032
クエリが返されなかったため、実際の実行プランを取得できませんでした。バグの可能性が高いようです。 (Microsoft SQL Server 2008 R2(RTM)-10.50.1600.1(X64)を実行)
1GBのサイズのデータファイルと3GBのログファイル(ラップトップマシン、両方のファイルが同じドライブにある)と100分に設定された回復間隔(チェックポイントによる結果のゆがみを回避するため)の単純な復旧モデルの新しいデータベースで単一行inserts
を使用した場合と同様の結果が得られます。
私は3つのケースをテストしました。それぞれのケースで、次の表に100,000行を個別に20バッチ挿入しました。 完全なスクリプトはこの回答の改訂履歴にあります 。
_CREATE TABLE TestGuid
(
Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100)
)
CREATE TABLE TestId
(
Id Int NOT NULL identity(1, 1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100)
)
CREATE TABLE TestInt
(
Id Int NOT NULL PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100)
)
_
3番目のテーブルでは、テストはId
の値を増分して行を挿入しましたが、これはループ内の変数の値を増分することによって自己計算されました。
20のバッチにかかる時間を平均すると、次の結果が得られました。
_NEWSEQUENTIALID() IDENTITY() INT
----------------- ----------- -----------
1999 2633 1878
_
したがって、結果の原因となっているのはidentity
作成プロセスのオーバーヘッドであるように思われます。自己計算された増分整数の場合、結果は、IOコストのみを考慮した場合に予想される結果と一致します。
上記の挿入コードをストアドプロシージャに入れて_sys.dm_exec_procedure_stats
_を確認すると、次の結果が得られます
_proc_name execution_count total_worker_time last_worker_time min_worker_time max_worker_time total_elapsed_time last_elapsed_time min_elapsed_time max_elapsed_time total_physical_reads last_physical_reads min_physical_reads max_physical_reads total_logical_writes last_logical_writes min_logical_writes max_logical_writes total_logical_reads last_logical_reads min_logical_reads max_logical_reads
-------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- -------------------- --------------------
IdentityInsert 20 45060360 2231067 2094063 2645079 45119362 2234067 2094063 2660080 0 0 0 0 32505 1626 1621 1626 6268917 315377 276833 315381
GuidInsert 20 34829052 1742052 1696051 1833055 34900053 1744052 1698051 1838055 0 0 0 0 35408 1771 1768 1772 6316837 316766 298386 316774
_
したがって、これらの結果では、_total_worker_time
_は約30%高くなっています。 これは表す
コンパイルされてからこのストアドプロシージャの実行によって消費されたCPU時間の合計(マイクロ秒単位)。
そのため、IDENTITY
値を生成するコードは、NEWSEQUENTIALID()
を生成するコードよりもCPUに負荷がかかるように見えます(2つの数値の差は10231308で、平均して約5µsです)挿入)。このテーブル定義の場合、この固定CPUコストは、キーの幅が広いために発生した追加の論理読み取りおよび書き込みを上回るほど高額でした。 (注:Itzik Ben Ganは ここでも同様のテスト を行い、挿入ごとに2µsのペナルティを発見しました)
IDENTITY
はUuidCreateSequential
よりもCPUに負荷がかかるのですか?これは説明されていると思います この記事では 。生成されたidentity
値の10番目ごとに、SQL Serverはディスク上のシステムテーブルに変更を書き込む必要があります
1つのステートメントに100,000行が挿入されたとき、GUID
の場合にはおそらくわずかな利点があるものの、明確な結果とはほど遠い違いがなくなったことがわかりました。私のテストでは20バッチの平均は
_NEWSEQUENTIALID() IDENTITY()
----------------- -----------
1016 1088
_
PhilのコードとMitchの最初の結果セットに明らかなペナルティがない理由は、多行挿入を行うために使用したコードがSELECT TOP (@NumRows)
を使用していたためです。これにより、オプティマイザは挿入される行数を正しく見積もることができませんでした。
(おそらく順次!)GUID
sに追加のソート操作を追加する特定の転換点があるため、これは利点のようです。
BOLの説明文 から、このソート操作は必要ありません。
GUIDこれは、任意の値よりも大きいGUID Windowsの起動以降、指定されたコンピューター上でこの関数によって以前に生成されたものです。Windowsの再起動後、GUIDは低い範囲から再開できますが、それでもグローバルに一意です。
したがって、SQL Serverがidentity
列に対してすでにそうであるように、計算スカラーの出力がすでに事前に並べ替えられていることをSQL Serverが認識しないことがバグまたは最適化の欠如のように思えました。 (編集 これを報告し、不要なソートの問題がDenaliで修正されました)
非常に単純です:GUIDを使用すると、IDENTITYの場合よりも、行の次の数値を生成する方が安価です(GUIDの現在の値を保存する必要はなく、IDENTITYを)これは、NEWSEQUENTIALGUIDの場合にも当てはまります。
テストをより公平にして、大きなCACHEを備えたSEQUENCERを使用できます。これはIDENTITYよりも安価です。
しかし、M.R。が言うように、GUIDにはいくつかの大きな利点があります。実際のところ、IDENTITYカラムよりもはるかにスケーラブルです(ただし、それらが順次でない場合のみ)。
参照: http://blog.kejser.org/2011/10/05/boosting-insert-speed-by-generating-scalable-keys/
私はこの種の質問に魅了されています。なぜ金曜日の夜に投稿しなければならなかったのですか? :)
テストがINSERTパフォーマンスの測定のみを目的としている場合でも、誤解を招く可能性のあるいくつかの要因(ループ、長時間実行トランザクションなど)を導入した可能性があります。
私のバージョンが何かを証明することを完全に確信しているわけではありませんが、IDはその中のGUIDよりも優れています(3.2秒vs自宅のPCでは6.8秒)。
SET NOCOUNT ON
CREATE TABLE TestGuid2 (Id UNIQUEIDENTIFIER NOT NULL DEFAULT NEWSEQUENTIALID() PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))
CREATE TABLE TestInt (Id Int NOT NULL identity(1,1) PRIMARY KEY,
SomeDate DATETIME, batchNumber BIGINT, FILLER CHAR(100))
DECLARE @Numrows INT = 1000000
CREATE TABLE #temp (Id int NOT NULL Identity(1,1) PRIMARY KEY, rowNum int)
DECLARE @LocalCounter INT = 0
--put rows into temp table
WHILE (@LocalCounter < @NumRows)
BEGIN
INSERT INTO #temp(rowNum) VALUES (@LocalCounter)
SET @LocalCounter += 1
END
--Do inserts using GUIDs
DECLARE @GUIDTimeStart DateTime = GETDATE()
INSERT INTO TestGuid2 (SomeDate, batchNumber)
SELECT GETDATE(), rowNum FROM #temp
DECLARE @GUIDTimeEnd DateTime = GETDATE()
--Do inserts using IDENTITY
DECLARE @IdTimeStart DateTime = GETDATE()
INSERT INTO TestInt (SomeDate, batchNumber)
SELECT GETDATE(), rowNum FROM #temp
DECLARE @IdTimeEnd DateTime = GETDATE()
SELECT DATEDIFF(ms, @IdTimeStart, @IdTimeEnd) AS IdTime
SELECT DATEDIFF(ms, @GUIDTimeStart, @GUIDTimeEnd) AS GuidTime
DROP TABLE TestGuid2
DROP TABLE TestInt
DROP TABLE #temp
サンプルスクリプトを数回実行して、バッチのカウントとサイズを調整しました(提供していただきありがとうございます)。
最初に、キーのパフォーマンスの1つの側面(INSERT
速度)のみを測定していると言います。したがって、できるだけ迅速にデータをテーブルに取り込むことだけに特に関心がない限り、この動物にはさらに多くのことがあります。
私の調査結果は一般的にあなたの調査結果と似ていました。ただし、INSERT
とGUID
(int)の間のIDENTITY
速度の差異はわずかより大きいであり、GUID
よりもwith IDENTITY
-実行間でおそらく+/- 10% IDENTITY
を使用したバッチの変動は、毎回2〜3%未満でした。
また、私のテストボックスは明らかにあなたのテストボックスよりも強力ではないので、小さい行数を使用する必要がありました。
この同じトピックについて、stackoverflowの別のコンバージョンを参照します https://stackoverflow.com/questions/170346/what-are-the-performance-improvement-of-sequential-guid-over -standard-guid
私が知っていることの1つは、シーケンシャルGUIDを持っていることは、リーフの動きがほとんどないためにインデックスの使用がより良いため、HDシークが減少することです。このため、キーを多数のページに分散する必要がないため、挿入も高速になると思います。
私の個人的な経験では、大規模な高トラフィックDBを実装する場合は、他のシステムとの統合のためのスケーラビリティが大幅に向上するため、GUIDを使用する方がよいということです。これは、特にレプリケーションとint/bigintの制限に当てはまります。bigintsが足りなくなるわけではありませんが、最終的にはサイクルバックします。