1

假设我有下表,列上有一个聚集索引(比如,a)

CREATE TABLE Tmp
(
    a int,
    constraint pk_a primary key clustered (a)
)

然后,假设我有两组非常多的行要插入到表中。

  • 第一组)值顺序增加(即,{0,1,2,3,4,5,6,7,8,9,..., 999999997, 999999998, 99999999})
  • 第二组)值按顺序递减(即 {99999999,999999998,999999997, ..., 3,2,1,0}

你认为在第一组和第二组中插入值会有性能差异吗?如果是这样,为什么?

谢谢

4

3 回答 3

5

SQL Server 通常会在插入之前尝试将大型插入排序为聚集索引顺序。

但是,如果插入的源是表变量,那么它将不考虑基数,除非在填充表变量后重新编译语句。如果没有这个,它将假设插入只会是一行。

下面的脚本演示了三种可能的情况。

  1. 插入源的顺序已经完全正确。
  2. 插入源的顺序正好相反。
  3. 插入源的顺序正好相反,但OPTION (RECOMPILE)使用它是为了让 SQL Server 编译一个适合插入 1,000,000 行的计划。

执行计划

计划

第三个有一个排序运算符,可以首先将插入的值按聚集索引顺序排列。

/*Create three separate identical tables*/
CREATE TABLE Tmp1(a int primary key clustered (a))
CREATE TABLE Tmp2(a int primary key clustered (a))
CREATE TABLE Tmp3(a int primary key clustered (a))

DBCC FREEPROCCACHE;

GO

DECLARE @Source TABLE (N INT PRIMARY KEY (N ASC))

INSERT INTO @Source
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY (SELECT 0)) 
FROM sys.all_columns c1, sys.all_columns c2, sys.all_columns c3

SET STATISTICS TIME ON;

PRINT 'Tmp1'
INSERT INTO Tmp1
SELECT TOP (1000000) N
FROM @Source
ORDER BY N

PRINT 'Tmp2'
INSERT INTO Tmp2
SELECT  TOP (1000000) 1000000 - N
FROM @Source
ORDER BY N

PRINT 'Tmp3'
INSERT INTO Tmp3
SELECT 1000000 - N
FROM @Source
ORDER BY N
OPTION (RECOMPILE)

SET STATISTICS TIME OFF;

验证结果并清理

SELECT object_name(object_id) AS name, 
       page_count, 
       avg_fragmentation_in_percent, 
       fragment_count, 
       avg_fragment_size_in_pages
FROM 
sys.dm_db_index_physical_stats(db_id(), object_id('Tmp1'), 1, NULL, 'DETAILED') 
WHERE  index_level = 0 
UNION ALL 
SELECT object_name(object_id) AS name, 
       page_count, 
       avg_fragmentation_in_percent, 
       fragment_count, 
       avg_fragment_size_in_pages
FROM 
sys.dm_db_index_physical_stats(db_id(), object_id('Tmp2'), 1, NULL, 'DETAILED') 
WHERE  index_level = 0 
UNION ALL 
SELECT object_name(object_id) AS name, 
       page_count, 
       avg_fragmentation_in_percent, 
       fragment_count, 
       avg_fragment_size_in_pages
FROM 
sys.dm_db_index_physical_stats(db_id(), object_id('Tmp3'), 1, NULL, 'DETAILED') 
WHERE  index_level = 0 

DROP TABLE Tmp1, Tmp2, Tmp3

STATISTICS TIME ON结果

+------+----------+--------------+
|      | CPU Time | Elapsed Time |
+------+----------+--------------+
| Tmp1 | 6718 ms  | 6775 ms      |
| Tmp2 | 7469 ms  | 7240 ms      |
| Tmp3 | 7813 ms  | 9318 ms      |
+------+----------+--------------+

分片结果

+------+------------+------------------------------+----------------+----------------------------+
| name | page_count | avg_fragmentation_in_percent | fragment_count | avg_fragment_size_in_pages |
+------+------------+------------------------------+----------------+----------------------------+
| Tmp1 |       3345 | 0.448430493                  |             17 | 196.7647059                |
| Tmp2 |       3345 | 99.97010463                  |           3345 | 1                          |
| Tmp3 |       3345 | 0.418535127                  |             16 | 209.0625                   |
+------+------------+------------------------------+----------------+----------------------------+

结论

在这种情况下,他们三个最终都使用了完全相同的页数。然而Tmp2,99.97% 是分散的,而其他两个只有 0.4%。插入Tmp3花费的时间最长,因为这首先需要一个额外的排序步骤,但是需要根据未来扫描最小碎片表的好处来设置这一时间成本。

Tmp2从下面的查询中可以看出碎片如此严重的原因

WITH T AS
(
SELECT TOP 3000 file_id, page_id, a
FROM Tmp2
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)
ORDER BY a
)
SELECT file_id, page_id, MIN(a), MAX(a)
FROM T 
group by file_id, page_id
ORDER BY MIN(a)

在逻辑碎片为零的情况下,具有下一个最高键值的页面将是文件中的下一个最高页面,但这些页面的顺序与它们应该是完全相反的。

+---------+---------+--------+--------+
| file_id | page_id | Min(a) | Max(a) |
+---------+---------+--------+--------+
|       1 |   26827 |      0 |    143 |
|       1 |   26826 |    144 |    442 |
|       1 |   26825 |    443 |    741 |
|       1 |   26824 |    742 |   1040 |
|       1 |   26823 |   1041 |   1339 |
|       1 |   26822 |   1340 |   1638 |
|       1 |   26821 |   1639 |   1937 |
|       1 |   26820 |   1938 |   2236 |
|       1 |   26819 |   2237 |   2535 |
|       1 |   26818 |   2536 |   2834 |
|       1 |   26817 |   2835 |   2999 |
+---------+---------+--------+--------+

行以降序到达,因此例如值 2834 到 2536 被放入页面 26818,然后为 2535 分配一个新页面,但这是页面 26819 而不是页面 26817。

Tmp2插入时间比插入时间长的一个可能原因Tmp1是,由于行在页面上以完全相反的顺序插入,因此每次插入Tmp2意味着页面上的插槽数组需要重写,所有先前的条目都向上移动以腾出空间新品到货。

于 2012-08-26T21:43:22.543 回答
0

要回答这个问题,您只需要查看聚类对数据的影响以及数据的逻辑排序方式。通过升序聚类,更高的数字被添加到表格的末尾;插入会非常快。反向插入时,会插入另外两条记录之间(分页时阅读);这将导致插入速度变慢。这实际上还有其他负面影响(阅读填充因子)。

于 2012-08-26T19:24:36.880 回答
0

它与按顺序分配页面有关,就像对聚集索引所做的那样。有了第一个,他们自然会聚集在一起。但在第二种情况下,我认为您必须不断移动页面位置才能使它们按顺序上升。但是,我真的只在概念层面上了解 SQL Server,因此您必须进行测试。

于 2012-08-26T20:13:54.590 回答