高效插入带有聚集索引的表

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 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')
;  
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 上运行。

插入测试涵盖三种情况:

  1. 按与目标相同的顺序暂存数据聚集索引。
  2. 以相反的顺序暂存数据聚集索引。
  3. 由包含随机 INT 的 col2 聚类的暂存数据。

第一次运行,插入 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 行。同样,改变页面密度和现有行数也没有影响。


gon*_*alu 9

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