高效插入具有聚簇索引的表


28

我有一条SQL语句,将行插入到表中,该表在TRACKING_NUMBER列上具有聚簇索引。

例如:

INSERT INTO TABL_NAME (TRACKING_NUMBER, COLB, COLC) 
SELECT TRACKING_NUMBER, COL_B, COL_C 
FROM STAGING_TABLE

我的问题是-在SELECT语句中为聚集索引列使用ORDER BY子句是否有帮助,或者ORDER BY子句所需的额外排序会否抵消获得的任何收益?

Answers:


18

正如其他答案已经表明的那样,SQL Server可能会或可能不会明确确保在行之前按聚集索引顺序对行进行排序insert

这取决于计划中的聚簇索引运算符是否具有DMLRequestSort属性集(而属性集又取决于所插入的行的估计数量)。

如果您发现SQL服务器被低估这个无论什么原因,你可能会从添加的明确受益ORDER BYSELECT从查询,以尽量减少页面拆分和随后的分裂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是否小于将排序添加到计划的阈值。但是,如以下注释中所指出的,这的确意味着插入将无法利用最少的日志记录。



12

优化器决定在插入之前对数据进行排序会更有效,它将在插入运算符的上游进行。如果您在查询中引入排序,那么优化器应意识到数据已被排序,因此不再进行排序。请注意,根据从暂存表插入的行数,选择的执行计划可能因运行而异。

如果可以使用或不使用显式排序来捕获流程的执行计划,请将它们附加到问题中以进行评论。

编辑:2011-10-28 17:00

@Gonsalu的答案似乎表明总是进行排序操作,而事实并非如此。需要演示脚本!

随着脚本变得越来越大,我将它们移至Gist。为了便于实验,这些脚本使用SQLCMD模式。测试在8K双核2K5SP3上运行。

插入测试涵盖三种情况:

  1. 分段数据聚簇索引的顺序与目标顺序相同。
  2. 登台数据聚簇索引的顺序相反。
  3. 由col2聚集的分段数据,其中包含一个随机INT。

第一次运行,插入25行。

第一轮25行

这三个执行计划都是相同的,在计划中的任何地方都不会发生排序,并且聚集索引扫描为“ ordered = false”。

第二次运行,插入26行。

第二轮,26行

这次计划有所不同。

  • 第一个将聚簇索引扫描显示为ordered = false。由于对源数据进行了适当的排序,因此未发生排序。
  • 在第二个中,聚簇索引向后扫描为ordered = true。因此,我们没有排序操作,但是优化器会识别需要排序的数据,并且它会以相反的顺序进行扫描。
  • 第三个显示了排序运算符。

因此,存在一个临界点,优化器认为这是必需的。如@MartinSmith所示,这似乎是基于要插入的估计行。在我的测试平台上,25不需要排序,26不需要排序(2K5SP3,双核,8GB)

SQLCMD脚本包含一些变量,这些变量允许在其他插入之前更改表中的行大小(更改页面密度)以及dbo.MyTable中的行数。根据我的测试,两者都不会对临界点产生任何影响。

如果有任何读者倾向,请运行脚本并添加引爆点作为注释。有兴趣了解它是否在测试平台和/或版本之间有所不同。

编辑:2011-10-28 20:15

使用2K8R2在同一钻机上重复测试。这次引爆点是251行。同样,改变页面密度和现有行数也无效。


8

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在这种情况下,该子句也是多余的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.