Answers:
正如其他答案已经表明的那样,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')
;
显示那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
但是对于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
相反,有时,当您知道数据已进行预排序并希望避免不必要的排序时,您可能希望强制SQL Server低估行数。一个值得注意的例子是使用newsequentialid
聚集索引键将大量行插入表中。在Denali之前的SQL Server版本中,SQL Server添加了不必要且可能昂贵的排序操作。这可以避免
DECLARE @var INT =2147483647
INSERT INTO Foo
SELECT TOP (@var) *
FROM Bar
然后,SQL Server将估计将插入100行,而不管其大小Bar
是否小于将排序添加到计划的阈值。但是,如以下注释中所指出的,这的确意味着插入将无法利用最少的日志记录。
优化器决定在插入之前对数据进行排序会更有效,它将在插入运算符的上游进行。如果您在查询中引入排序,那么优化器应意识到数据已被排序,因此不再进行排序。请注意,根据从暂存表插入的行数,选择的执行计划可能因运行而异。
如果可以使用或不使用显式排序来捕获流程的执行计划,请将它们附加到问题中以进行评论。
编辑:2011-10-28 17:00
@Gonsalu的答案似乎表明总是进行排序操作,而事实并非如此。需要演示脚本!
随着脚本变得越来越大,我将它们移至Gist。为了便于实验,这些脚本使用SQLCMD模式。测试在8K双核2K5SP3上运行。
插入测试涵盖三种情况:
第一次运行,插入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;
让我们启用实际查询计划的文本显示,以便我们查看查询处理器执行的任务。
SET STATISTICS PROFILE ON;
GO
现在,让我们INSERT
在没有ORDER BY
子句的情况下将2K行插入表中。
INSERT INTO #Test
SELECT number
FROM #Sequence
该查询的实际执行计划如下。
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]))
如您所见,在实际的INSERT发生之前有一个Sort运算符。
现在,让我们清除表,并INSERT
使用该ORDER BY
子句将2k行插入表中。
TRUNCATE TABLE #Test;
GO
INSERT INTO #Test
SELECT number
FROM #Sequence
ORDER BY number
该查询的实际执行计划如下。
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]))
请注意,这是INSERT
不带ORDER BY
子句的语句所使用的执行计划。
现在,Sort
并不总是需要该操作,正如Mark Smith在另一个答案中所示(如果要插入的行数少),但是ORDER BY
在这种情况下该子句仍然是多余的,因为即使使用显式ORDER BY
,也不会Sort
生成任何操作由查询处理器处理。
您可以INSERT
通过使用最少日志记录来优化具有聚集索引的表中的语句INSERT
,但这超出了此问题的范围。
2011年11月2日更新: 如Mark Smith所示,INSERT
可能不一定总是将s放入具有聚集索引的表中-但是,ORDER BY
在这种情况下,该子句也是多余的。