GWR*_*GWR 31 performance sql-server clustered-index insert
我有一个 SQL 语句,该语句将行插入到表中,并且在 TRACKING_NUMBER 列上具有聚集索引。
例如:
INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC)
SELECT TRACKING_NUMBER, COL_B, COL_C
FROM STAGING_TABLE
Run Code Online (Sandbox Code Playgroud)
我的问题是 - 在聚集索引列的 SELECT 语句中使用 ORDER BY 子句是否有帮助,或者是否会因 ORDER BY 子句所需的额外排序而否定任何获得的收益?
Mar*_*ith 20
由于其他答案已经表明 SQL Server 可能会也可能不会明确确保行在insert.
这取决于计划中的聚集索引运算符是否具有该DMLRequestSort属性集(这又取决于估计的插入行数)。
如果您发现SQL服务器被低估这个无论什么原因,你可能会从添加的明确受益ORDER BY于SELECT从查询,以尽量减少页面拆分和随后的分裂INSERT操作
例子:
use tempdb;
GO
CREATE TABLE T(N INT PRIMARY KEY,Filler char(2000))
CREATE TABLE T2(N INT PRIMARY KEY,Filler char(2000))
GO
DECLARE @T TABLE (U UNIQUEIDENTIFIER PRIMARY KEY DEFAULT NEWID(),N int)
INSERT INTO @T(N)
SELECT number
FROM master..spt_values
WHERE type = 'P' AND number BETWEEN 0 AND 499
/*Estimated row count wrong as inserting from table variable*/
INSERT INTO T(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
/*Same operation using explicit sort*/
INSERT INTO T2(N)
SELECT T1.N*1000 + T2.N
FROM @T T1, @T T2
ORDER BY T1.N*1000 + T2.N
SELECT avg_fragmentation_in_percent,
fragment_count,
page_count,
avg_page_space_used_in_percent,
record_count
FROM sys.dm_db_index_physical_stats(2, OBJECT_ID('T'), NULL, NULL, 'DETAILED')
;
SELECT avg_fragmentation_in_percent,
fragment_count,
page_count,
avg_page_space_used_in_percent,
record_count
FROM sys.dm_db_index_physical_stats(2, OBJECT_ID('T2'), NULL, NULL, 'DETAILED')
;
Run Code Online (Sandbox Code Playgroud)
显示T大量碎片化
avg_fragmentation_in_percent fragment_count page_count avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
99.3116118225536 92535 92535 67.1668272794663 250000
99.5 200 200 74.2868173956017 92535
0 1 1 32.0978502594514 200
Run Code Online (Sandbox Code Playgroud)
但对于T2碎片化是最小的
avg_fragmentation_in_percent fragment_count page_count avg_page_space_used_in_percent record_count
---------------------------- -------------------- -------------------- ------------------------------ --------------------
0.376 262 62500 99.456387447492 250000
2.1551724137931 232 232 43.2438349394613 62500
0 1 1 37.2374598468001 232
Run Code Online (Sandbox Code Playgroud)
相反,有时当您知道数据已经预先排序并希望避免不必要的排序时,您可能希望强制 SQL Server 低估行数。一个值得注意的例子是将大量行插入具有newsequentialid聚集索引键的表中。在 Denali SQL Server 之前的 SQL Server 版本中,添加了不必要且可能昂贵的排序操作。这可以通过以下方式避免
DECLARE @var INT =2147483647
INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar
Run Code Online (Sandbox Code Playgroud)
然后 SQL Server 将估计将插入 100 行,而不管其大小Bar低于将排序添加到计划的阈值。然而,正如下面的评论中指出的那样,这确实意味着插入将不幸地无法利用最小日志记录。
Mar*_*ith 13
如果优化器决定在插入之前对数据进行排序会更有效,它将在插入运算符上游的某个地方这样做。如果您将排序作为查询的一部分,优化器应该意识到数据已经排序并省略再次排序。请注意,选择的执行计划可能因运行而异,具体取决于从临时表插入的行数。
如果您可以在使用和不使用显式排序的情况下捕获流程的执行计划,请将它们附加到您的问题以供评论。
编辑:2011-10-28 17:00
@Gonsalu 的回答似乎表明排序操作总是会发生,但事实并非如此。需要演示脚本!
由于脚本变得非常大,我已将它们移至Gist。为便于实验,脚本使用 SQLCMD 模式。测试在 2K5SP3、双核、8GB 上运行。
插入测试涵盖三种情况:
第一次运行,插入 25 行。

所有三个执行计划都是相同的,计划中的任何地方都没有发生排序,并且聚集索引扫描是“ordered=false”。
第二次运行,插入 26 行。

这次计划不同。
因此,有一个临界点,优化器认为某种类型是必要的。正如@MartinSmith 所示,这似乎是基于要插入的估计行数。在我的测试台上,25 不需要排序,26 需要(2K5SP3,双核,8GB)
SQLCMD 脚本包括允许在附加插入之前更改表中行的大小(改变页面密度)和 dbo.MyTable 中的行数的变量。从我的测试来看,两者都对临界点没有任何影响。
如果任何读者如此倾向,请运行脚本并添加您的引爆点作为评论。有兴趣了解它是否因测试台和/或版本而异。
编辑:2011-10-28 20:15
在同一台设备上重复测试,但使用 2K8R2。这次的临界点是 251 行。同样,改变页面密度和现有行数也没有影响。
将ORDER BY在该条款SELECT的语句是多余的。
这是多余的,因为要插入的行,如果需要排序,无论如何都会排序。
让我们创建一个测试用例。
CREATE TABLE #Test (
id INTEGER NOT NULL
);
CREATE UNIQUE CLUSTERED INDEX CL_Test_ID ON #Test (id);
CREATE TABLE #Sequence (
number INTEGER NOT NULL
);
INSERT INTO #Sequence
SELECT number FROM master..spt_values WHERE name IS NULL;
Run Code Online (Sandbox Code Playgroud)
让我们启用实际查询计划的文本显示,以便我们可以看到查询处理器执行了哪些任务。
SET STATISTICS PROFILE ON;
GO
Run Code Online (Sandbox Code Playgroud)
现在,让我们INSERT在没有ORDER BY子句的情况下将 2K 行放入表中。
INSERT INTO #Test
SELECT number
FROM #Sequence
Run Code Online (Sandbox Code Playgroud)
此查询的实际执行计划如下。
INSERT INTO #Test SELECT number FROM #Sequence
|--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
|--Top(ROWCOUNT est 0)
|--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
|--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))
Run Code Online (Sandbox Code Playgroud)
如您所见,在实际 INSERT 发生之前有一个 Sort 运算符。
现在,让我们清除表,并INSERT使用ORDER BY子句将 2k 行放入表中。
TRUNCATE TABLE #Test;
GO
INSERT INTO #Test
SELECT number
FROM #Sequence
ORDER BY number
Run Code Online (Sandbox Code Playgroud)
此查询的实际执行计划如下。
INSERT INTO #Test SELECT number FROM #Sequence ORDER BY number
|--Clustered Index Insert(OBJECT:([tempdb].[dbo].[#Test]), SET:([tempdb].[dbo].[#Test].[id] = [tempdb].[dbo].[#Sequence].[number]))
|--Top(ROWCOUNT est 0)
|--Sort(ORDER BY:([tempdb].[dbo].[#Sequence].[number] ASC))
|--Table Scan(OBJECT:([tempdb].[dbo].[#Sequence]))
Run Code Online (Sandbox Code Playgroud)
请注意,它与INSERT没有ORDER BY子句的语句所使用的执行计划相同。
现在,Sort并不总是需要该操作,正如 Mark Smith 在另一个答案中所示(如果要插入的行数很少),但ORDER BY在这种情况下,该子句仍然是多余的,因为即使使用显式ORDER BY,也不会Sort生成任何操作由查询处理器。
您可以INSERT使用最小日志记录将语句优化为带有聚集索引的表INSERT,但这超出了本问题的范围。
2011 年 11 月 2 日更新: 正如 Mark Smith 所展示的那样,INSERTs 进入具有聚集索引的表可能并不总是需要排序——ORDER BY不过,在这种情况下,该子句也是多余的。
| 归档时间: |
|
| 查看次数: |
68339 次 |
| 最近记录: |