Inv*_*con 6 performance sql-server clustered-index insert
这与这个问题有关:Efficient INSERT INTO a Table With Clustered Index
从这个问题来看,SQL Server 有时会在将多个值插入具有聚集索引的表中以减少碎片时对数据进行预排序。当我使用表值构造函数在插入语句中插入多个值时,SQL Server 也会这样做吗?
例如,如果我有一个event_log聚集在一timestamp列上的表,我插入这样的数据:
INSERT INTO event_log (timestamp, data)
VALUES
('03:00:02', 'data string...'),
('03:00:01', 'data string...'),
('03:00:00', 'data string...')
Run Code Online (Sandbox Code Playgroud)
SQL Server 是否会为我预先排序以确保数据有效地进入而不会产生碎片,还是应该在生成 SQL 之前对应用程序中的数据进行排序?
我已经构建了一个测试台来看看会发生什么:
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插入语句添加 and子句并不能保证插入会按该顺序发生。例如,考虑如果插入平行会发生什么。
在非生产系统上,您可以使用跟踪标志 2332 作为插入语句的选项,以“强制”SQL Server 在插入之前对输入进行排序。 @PaulWhite有一篇有趣的文章,优化 T-SQL 查询,更改覆盖该查询的数据以及其他详细信息。请注意,该跟踪标志不受支持,不应在生产系统中使用,因为这可能会使您的保修失效。在非生产系统中,为了您自己的教育,您可以尝试在INSERT语句末尾添加以下内容:
OPTION (QUERYTRACEON 2332);
Run Code Online (Sandbox Code Playgroud)
将其附加到插入后,查看计划,您将看到显式排序:
如果 Microsoft 将此作为受支持的跟踪标志,那就太好了。
Paul White 让我意识到SQL Server会在它认为有用时自动将排序运算符引入计划中。对于上面的示例查询,如果我在values子句中使用 250 个项目运行插入,则不会自动实现排序。但是,对于 251 项,SQL Server 会在插入之前自动对值进行排序。为什么截止是 250/251 行对我来说仍然是个谜,除了它似乎是硬编码的。如果我将SomeData列中插入的数据的大小减少到一个字节,截止值仍然是250/251 行,即使在这两种情况下表的大小都只是一页。有趣的是,查看 insert withSET STATISTICS IO, TIME ON;显示了带有单个字节的插入SomeData value 排序时需要两倍的时间。
没有排序(即插入 250 行):
SQL Server 解析和编译时间: CPU 时间 = 0 毫秒,经过时间 = 0 毫秒。 SQL Server 解析和编译时间: CPU 时间 = 16 毫秒,经过时间 = 16 毫秒。 SQL Server 解析和编译时间: CPU 时间 = 0 毫秒,经过时间 = 0 毫秒。 表“测试排序”。扫描计数 0,逻辑读取 501,物理读取 0, 预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读为 0。 (250 行受影响) (1 行受影响) SQL Server 执行时间: CPU 时间 = 0 毫秒,经过时间 = 11 毫秒。
使用排序(即插入 251 行):
SQL Server 解析和编译时间: CPU 时间 = 0 毫秒,经过时间 = 0 毫秒。 SQL Server 解析和编译时间: CPU 时间 = 15 毫秒,经过时间 = 17 毫秒。 SQL Server 解析和编译时间: CPU 时间 = 0 毫秒,经过时间 = 0 毫秒。 表“测试排序”。扫描计数 0,逻辑读取 503,物理读取 0, 预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读为 0。 表“工作台”。扫描计数 0,逻辑读取 0,物理读取 0, 预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读为 0。 (251 行受影响) (1 行受影响) SQL Server 执行时间: CPU 时间 = 16 毫秒,经过时间 = 21 毫秒。
一旦开始增加行大小,排序版本肯定会变得更有效率。将 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上面的语句中。