som*_*ame 39 sql-server clustered-index
在问了这个比较顺序和非顺序 GUID 的问题后,我尝试比较 INSERT 性能在 1) 一个带有顺序初始化的 GUID 主键newsequentialid()的表,和 2) 一个带有顺序初始化的 INT 主键的表identity(1,1)。我希望后者最快,因为整数的宽度较小,并且生成顺序整数似乎比顺序 GUID 更简单。但令我惊讶的是,带有整数键的表上的 INSERT 比顺序 GUID 表慢得多。
这显示了测试运行的平均时间使用 (ms):
NEWSEQUENTIALID() 1977
IDENTITY() 2223
Run Code Online (Sandbox Code Playgroud)
谁能解释一下?
使用了以下实验:
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
Run Code Online (Sandbox Code Playgroud)
更新: 修改脚本以根据 TEMP 表执行插入,就像下面 Phil Sandler、Mitch Wheat 和 Martin 的例子一样,我还发现 IDENTITY 应该更快。但这不是插入行的传统方式,我仍然不明白为什么一开始实验会出错:即使我从原始示例中省略了 GETDATE(),IDENTITY() 仍然要慢得多。因此,使 IDENTITY() 优于 NEWSEQUENTIALID() 的唯一方法似乎是准备要插入到临时表中的行,并使用此临时表将许多插入作为批量插入执行。总而言之,我认为我们没有找到对这种现象的解释,而且 IDENTITY() 对于大多数实际用法似乎仍然较慢。谁能解释一下?
Mit*_*eat 19
我修改了@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
Run Code Online (Sandbox Code Playgroud)
使用的代码:
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
Run Code Online (Sandbox Code Playgroud)
在阅读@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()
...
Run Code Online (Sandbox Code Playgroud)
以下是计时结果:
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
Run Code Online (Sandbox Code Playgroud)
我无法获得实际的执行计划,因为查询从未返回!看起来很可能是一个错误。(运行 Microsoft SQL Server 2008 R2 (RTM) - 10.50.1600.1 (X64))
Mar*_*ith 19
在简单恢复模型中的新数据库上,数据文件大小为 1GB,日志文件大小为 3GB(笔记本电脑,两个文件在同一驱动器上)并且恢复间隔设置为 100 分钟(以避免检查点扭曲结果)我看到与单行的结果相似inserts。
我测试了三种情况:对于每种情况,我做了 20 批,分别将 100,000 行插入到下表中。完整的脚本可以在这个答案的修订历史中找到。
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)
)
Run Code Online (Sandbox Code Playgroud)
对于第三个表,测试插入了具有递增Id值的行,但这是通过在循环中递增变量值来自行计算的。
对 20 个批次所花费的时间进行平均得出以下结果。
NEWSEQUENTIALID() IDENTITY() INT
----------------- ----------- -----------
1999 2633 1878
Run Code Online (Sandbox Code Playgroud)
因此,它肯定似乎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
Run Code Online (Sandbox Code Playgroud)
所以在这些结果total_worker_time中大约高出 30%。这代表
自编译以来执行此存储过程所消耗的 CPU 时间总量(以微秒为单位)。
所以看起来好像生成IDENTITY值的代码比生成值的代码更NEWSEQUENTIALID()占用CPU (两个数字之间的差异是 10231308,平均每个插入大约 5μs。)并且对于这个表定义,这个固定的 CPU 成本足够高,足以超过由于密钥宽度较大而导致的额外逻辑读取和写入。(注意:Itzik Ben Gan 在这里做了类似的测试,发现每个插入有 2µs 的惩罚)
IDENTITY比 CPU 密集度更高UuidCreateSequential?我相信这在这篇文章中有解释。对于identity生成的每十分之一值,SQL Server 必须将更改写入磁盘上的系统表
当在单个语句中插入 100,000 行时,我发现差异消失了,可能仍然对GUID案例有一点好处,但远不及清晰的结果。我的测试中 20 个批次的平均值是
NEWSEQUENTIALID() IDENTITY()
----------------- -----------
1016 1088
Run Code Online (Sandbox Code Playgroud)
在 Phil 的代码和 Mitch 的第一组结果中没有明显的惩罚的原因是,碰巧我用来进行多行插入的代码使用了SELECT TOP (@NumRows). 这阻止了优化器正确估计将插入的行数。
这似乎是有益的,因为存在某个临界点,它将为(据说是顺序的!)GUIDs添加额外的排序操作。

BOL 中的说明文本不需要此排序操作。
创建一个 GUID,该 GUID 大于自 Windows 启动以来此函数在指定计算机上先前生成的任何 GUID。重新启动 Windows 后,GUID 可以从较低的范围再次启动,但仍然是全局唯一的。
因此,在我看来,SQL Server 无法识别计算标量的输出已经预先排序,因为它显然已经对identity列进行了预先排序,这在我看来是一个错误或缺少优化。(编辑我报告了这一点,现在在 Denali 中修复了不必要的排序问题)
很简单:使用 GUID,生成行中的下一个数字比生成 IDENTITY 便宜(不必存储 GUID 的当前值,必须存储 IDENTITY)。即使对于 NEWSEQUENTIALGUID 也是如此。
您可以使测试更公平,并使用带有大缓存的 SEQUENCER - 这比 IDENTITY 便宜。
但正如 MR 所说,GUID 有一些主要优势。事实上,它们比 IDENTITY 列更具可扩展性(但前提是它们不是连续的)。
参见:http : //blog.kejser.org/2011/10/05/boosting-insert-speed-by-generate-scalable-keys/
小智 3
我多次运行了您的示例脚本,对批次计数和大小进行了一些调整(非常感谢您提供它)。
首先,我要说的是,您仅测量按键性能的一个方面 -INSERT速度。因此,除非您特别关心尽快将数据输入表格,否则这种动物的作用还有很多。
我的发现总体上与你的相似。然而,我要提到的是,和(int)INSERT之间的速度差异比 with稍大——运行之间可能有+/- 10%。每次使用的批次变化小于 2 - 3%。GUIDIDENTITYGUIDIDENTITYIDENTITY
另请注意,我的测试盒显然不如您的强大,因此我不得不使用较小的行数。
| 归档时间: |
|
| 查看次数: |
11490 次 |
| 最近记录: |