HCL*_*HCL 19 sql sql-server indexing clustered-index sql-server-2008
SQL Server 2008+聚簇索引的排序顺序是否会影响插入性能?
特定情况下的数据类型是integer,插入的值是升序(Identity).因此,索引的排序顺序将与要插入的值的排序顺序相反.
我的猜测是,它会产生影响,但我不知道,也许SQL Server对这种情况有一些优化,或者它的内部数据存储格式对此无动于衷.
请注意,问题是关于INSERT性能,而不是SELECT.
更新
要更清楚地了解这个问题:当要插入的值integer(ASC)与聚簇索引(DESC)的排序顺序相反时会发生什么?
它们是有区别的.插入群集顺序会导致大量碎片.
当您运行以下代码时,DESC聚集索引将在NONLEAF级别生成其他UPDATE操作.
CREATE TABLE dbo.TEST_ASC(ID INT IDENTITY(1,1)
,RandNo FLOAT
);
GO
CREATE CLUSTERED INDEX cidx ON dbo.TEST_ASC(ID ASC);
GO
CREATE TABLE dbo.TEST_DESC(ID INT IDENTITY(1,1)
,RandNo FLOAT
);
GO
CREATE CLUSTERED INDEX cidx ON dbo.TEST_DESC(ID DESC);
GO
INSERT INTO dbo.TEST_ASC VALUES(RAND());
GO 100000
INSERT INTO dbo.TEST_DESC VALUES(RAND());
GO 100000
Run Code Online (Sandbox Code Playgroud)
两个Insert语句产生完全相同的执行计划,但在查看操作统计数据时,差异显示在[nonleaf_update_count]上.
SELECT
OBJECT_NAME(object_id)
,*
FROM sys.dm_db_index_operational_stats(DB_ID(),OBJECT_ID('TEST_ASC'),null,null)
UNION
SELECT
OBJECT_NAME(object_id)
,*
FROM sys.dm_db_index_operational_stats(DB_ID(),OBJECT_ID('TEST_DESC'),null,null)
Run Code Online (Sandbox Code Playgroud)
当SQL使用针对IDENTITY运行的DESC索引时,会发生额外的操作.这是因为DESC表变得碎片化(在页面开头插入的行)并且发生了额外的更新以维护B树结构.
关于这个例子最引人注目的是DESC聚集索引的碎片超过99%.这会重现与使用聚簇索引的随机GUID相同的不良行为. 以下代码演示了碎片.
SELECT
OBJECT_NAME(object_id)
,*
FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID('dbo.TEST_ASC'), NULL, NULL ,NULL)
UNION
SELECT
OBJECT_NAME(object_id)
,*
FROM sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID('dbo.TEST_DESC'), NULL, NULL ,NULL)
Run Code Online (Sandbox Code Playgroud)
更新:
在某些测试环境中,我也看到DESC表受到更多WAITS的影响,并增加了[page_io_latch_wait_count]和[page_io_latch_wait_in_ms]
更新:
当SQL可以执行向后扫描时,已经出现了关于降序索引的重点的讨论.请阅读本文关于Backward Scans的限制.
插入到聚簇索引中的值的顺序肯定会影响索引的性能,可能会产生大量碎片,并且还会影响插入本身的性能.
我构建了一个试验台,看看会发生什么:
USE tempdb;
CREATE TABLE dbo.TestSort
(
Sorted INT NOT NULL
CONSTRAINT PK_TestSort
PRIMARY KEY CLUSTERED
, SomeData VARCHAR(2048) NOT NULL
);
INSERT INTO dbo.TestSort (Sorted, SomeData)
VALUES (1797604285, CRYPT_GEN_RANDOM(1024))
, (1530768597, CRYPT_GEN_RANDOM(1024))
, (1274169954, CRYPT_GEN_RANDOM(1024))
, (-1972758125, CRYPT_GEN_RANDOM(1024))
, (1768931454, CRYPT_GEN_RANDOM(1024))
, (-1180422587, CRYPT_GEN_RANDOM(1024))
, (-1373873804, CRYPT_GEN_RANDOM(1024))
, (293442810, CRYPT_GEN_RANDOM(1024))
, (-2126229859, CRYPT_GEN_RANDOM(1024))
, (715871545, CRYPT_GEN_RANDOM(1024))
, (-1163940131, CRYPT_GEN_RANDOM(1024))
, (566332020, CRYPT_GEN_RANDOM(1024))
, (1880249597, CRYPT_GEN_RANDOM(1024))
, (-1213257849, CRYPT_GEN_RANDOM(1024))
, (-155893134, CRYPT_GEN_RANDOM(1024))
, (976883931, CRYPT_GEN_RANDOM(1024))
, (-1424958821, CRYPT_GEN_RANDOM(1024))
, (-279093766, CRYPT_GEN_RANDOM(1024))
, (-903956376, CRYPT_GEN_RANDOM(1024))
, (181119720, CRYPT_GEN_RANDOM(1024))
, (-422397654, CRYPT_GEN_RANDOM(1024))
, (-560438983, CRYPT_GEN_RANDOM(1024))
, (968519165, CRYPT_GEN_RANDOM(1024))
, (1820871210, CRYPT_GEN_RANDOM(1024))
, (-1348787729, CRYPT_GEN_RANDOM(1024))
, (-1869809700, CRYPT_GEN_RANDOM(1024))
, (423340320, CRYPT_GEN_RANDOM(1024))
, (125852107, CRYPT_GEN_RANDOM(1024))
, (-1690550622, CRYPT_GEN_RANDOM(1024))
, (570776311, CRYPT_GEN_RANDOM(1024))
, (2120766755, CRYPT_GEN_RANDOM(1024))
, (1123596784, CRYPT_GEN_RANDOM(1024))
, (496886282, CRYPT_GEN_RANDOM(1024))
, (-571192016, CRYPT_GEN_RANDOM(1024))
, (1036877128, CRYPT_GEN_RANDOM(1024))
, (1518056151, CRYPT_GEN_RANDOM(1024))
, (1617326587, CRYPT_GEN_RANDOM(1024))
, (410892484, CRYPT_GEN_RANDOM(1024))
, (1826927956, CRYPT_GEN_RANDOM(1024))
, (-1898916773, CRYPT_GEN_RANDOM(1024))
, (245592851, CRYPT_GEN_RANDOM(1024))
, (1826773413, CRYPT_GEN_RANDOM(1024))
, (1451000899, CRYPT_GEN_RANDOM(1024))
, (1234288293, CRYPT_GEN_RANDOM(1024))
, (1433618321, CRYPT_GEN_RANDOM(1024))
, (-1584291587, CRYPT_GEN_RANDOM(1024))
, (-554159323, CRYPT_GEN_RANDOM(1024))
, (-1478814392, CRYPT_GEN_RANDOM(1024))
, (1326124163, CRYPT_GEN_RANDOM(1024))
, (701812459, CRYPT_GEN_RANDOM(1024));
Run Code Online (Sandbox Code Playgroud)
第一列是主键,您可以看到值以随机(ish)顺序列出.以随机顺序列出值应该使SQL Server成为:
该CRYPT_GEN_RANDOM()函数用于每行生成1024字节的随机数据,以允许此表使用多个页面,从而使我们能够看到碎片插入的效果.
运行上面的插入后,您可以像这样检查碎片:
SELECT *
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('TestSort'), 1, 0, 'SAMPLED') ips;
Run Code Online (Sandbox Code Playgroud)
在我的SQL Server 2012 Developer Edition实例上运行此操作会显示90%的平均碎片,表明SQL Server在插入期间没有排序.
这个特定故事的寓意可能是,"如果有疑问,可以分类,如果它会有益".话虽如此,ORDER BY在insert语句中添加和子句并不能保证插入按顺序发生.考虑插入并行时会发生什么,例如.
在非生产系统上,您可以使用跟踪标志2332作为insert语句的选项,以"强制"SQL Server在插入之前对输入进行排序. @PaulWhite有一篇有趣的文章,优化T-SQL查询,改变覆盖该数据的数据,以及其他细节.请注意,该跟踪标志不受支持,不应在生产系统中使用,因为这可能会使保修失效.在非生产系统中,对于您自己的教育,您可以尝试将其添加到INSERT语句的末尾:
OPTION (QUERYTRACEON 2332);
Run Code Online (Sandbox Code Playgroud)
将附加到插入后,查看计划,您将看到一个明确的排序:
如果微软将这个作为支持的跟踪标志,那将是很好的.
保罗·怀特让我知道的SQL Server 不会自动引入一种运营商进入该计划时,它认为一个会有所帮助.对于上面的示例查询,如果我在values子句中运行带有250个项目的插入,则不会自动执行排序.但是,在251项中,SQL Server会在插入之前自动对值进行排序.为什么截止值是250/251行对我来说仍然是一个谜,除了它似乎是硬编码.如果我将SomeData列中插入的数据的大小减少到只有一个字节,则截止仍然是250/251行,即使两种情况下表的大小只是一个页面.有趣的是,查看插入SET STATISTICS IO, TIME ON;显示插入的单字节SomeData值在排序时需要两倍的时间.
没有排序(即插入250行):
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server parse and compile time: CPU time = 16 ms, elapsed time = 16 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. Table 'TestSort'. Scan count 0, logical reads 501, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. (250 row(s) affected) (1 row(s) affected) SQL Server Execution Times: CPU time = 0 ms, elapsed time = 11 ms.
排序(即插入251行):
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. SQL Server parse and compile time: CPU time = 15 ms, elapsed time = 17 ms. SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 0 ms. Table 'TestSort'. Scan count 0, logical reads 503, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. (251 row(s) affected) (1 row(s) affected) SQL Server Execution Times: CPU time = 16 ms, elapsed time = 21 ms.
一旦开始增加行大小,排序版本肯定会变得更有效.当插入4096个字节时SomeData,排序的插入在我的测试装备上几乎是未排序插入的两倍.
作为附注,如果您感兴趣,我VALUES (...)使用此T-SQL 生成了子句:
;WITH s AS (
SELECT v.Item
FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(Item)
)
, v AS (
SELECT Num = CONVERT(int, CRYPT_GEN_RANDOM(10), 0)
)
, o AS (
SELECT v.Num
, rn = ROW_NUMBER() OVER (PARTITION BY v.Num ORDER BY NEWID())
FROM s s1
CROSS JOIN s s2
CROSS JOIN s s3
CROSS JOIN v
)
SELECT TOP(50) ', ('
+ REPLACE(CONVERT(varchar(11), o.Num), '*', '0')
+ ', CRYPT_GEN_RANDOM(1024))'
FROM o
WHERE rn = 1
ORDER BY NEWID();
Run Code Online (Sandbox Code Playgroud)
这将生成1,000个随机值,仅选择第一列中具有唯一值的前50行.我将输出复制并粘贴到INSERT上面的语句中.