SQL Server 2008+聚簇索引的排序顺序

HCL*_*HCL 19 sql sql-server indexing clustered-index sql-server-2008

SQL Server 2008+聚簇索引的排序顺序是否会影响插入性能?

特定情况下的数据类型是integer,插入的值是升序(Identity).因此,索引的排序顺序将与要插入的值的排序顺序相反.

我的猜测是,它会产生影响,但我不知道,也许SQL Server对这种情况有一些优化,或者它的内部数据存储格式对此无动于衷.

请注意,问题是关于INSERT性能,而不是SELECT.

更新
要更清楚地了解这个问题:当要插入的值integer(ASC)与聚簇索引(DESC)的排序顺序相反时会发生什么?

pac*_*ely 7

它们是有区别的.插入群集顺序会导致大量碎片.

当您运行以下代码时,DESC聚集索引将在NONLEAF级别生成其他UPDATE操作.

CREATE TABLE dbo.TEST_ASC(ID INT IDENTITY(1,1) 
                            ,RandNo FLOAT
                            );
GO
CREATE CLUSTERED INDEX cidx ON dbo.TEST_ASC(ID ASC);
GO

CREATE TABLE dbo.TEST_DESC(ID INT IDENTITY(1,1) 
                            ,RandNo FLOAT
                            );
GO
CREATE CLUSTERED INDEX cidx ON dbo.TEST_DESC(ID DESC);
GO

INSERT INTO dbo.TEST_ASC VALUES(RAND());
GO 100000

INSERT INTO dbo.TEST_DESC VALUES(RAND());
GO 100000
Run Code Online (Sandbox Code Playgroud)

两个Insert语句产生完全相同的执行计划,但在查看操作统计数据时,差异显示在[nonleaf_update_count]上.

SELECT 
OBJECT_NAME(object_id)
,* 
FROM sys.dm_db_index_operational_stats(DB_ID(),OBJECT_ID('TEST_ASC'),null,null)
UNION
SELECT 
OBJECT_NAME(object_id)
,* 
FROM sys.dm_db_index_operational_stats(DB_ID(),OBJECT_ID('TEST_DESC'),null,null)
Run Code Online (Sandbox Code Playgroud)

当SQL使用针对IDENTITY运行的DESC索引时,会发生额外的操作.这是因为DESC表变得碎片化(在页面开头插入的行)并且发生了额外的更新以维护B树结构.

关于这个例子最引人注目的是DESC聚集索引的碎片超过99%.这会重现与使用聚簇索引的随机GUID相同的不良行为. 以下代码演示了碎片.

SELECT 
OBJECT_NAME(object_id)
,* 
FROM sys.dm_db_index_physical_stats  (DB_ID(), OBJECT_ID('dbo.TEST_ASC'), NULL, NULL ,NULL) 
UNION
SELECT 
OBJECT_NAME(object_id)
,* 
FROM sys.dm_db_index_physical_stats  (DB_ID(), OBJECT_ID('dbo.TEST_DESC'), NULL, NULL ,NULL) 
Run Code Online (Sandbox Code Playgroud)

更新:

在某些测试环境中,我也看到DESC表受到更多WAITS的影响,并增加了[page_io_latch_wait_count]和[page_io_latch_wait_in_ms]

更新:

当SQL可以执行向后扫描时,已经出现了关于降序索引的重点的讨论.请阅读本文关于Backward Scans限制.


Max*_*non 7

插入到聚簇索引中的值的顺序肯定会影响索引的性能,可能会产生大量碎片,并且还会影响插入本身的性能.

我构建了一个试验台,看看会发生什么:

USE tempdb;

CREATE TABLE dbo.TestSort
(
    Sorted INT NOT NULL
        CONSTRAINT PK_TestSort
        PRIMARY KEY CLUSTERED
    , SomeData VARCHAR(2048) NOT NULL
);

INSERT INTO dbo.TestSort (Sorted, SomeData)
VALUES  (1797604285, CRYPT_GEN_RANDOM(1024))
    , (1530768597, CRYPT_GEN_RANDOM(1024))
    , (1274169954, CRYPT_GEN_RANDOM(1024))
    , (-1972758125, CRYPT_GEN_RANDOM(1024))
    , (1768931454, CRYPT_GEN_RANDOM(1024))
    , (-1180422587, CRYPT_GEN_RANDOM(1024))
    , (-1373873804, CRYPT_GEN_RANDOM(1024))
    , (293442810, CRYPT_GEN_RANDOM(1024))
    , (-2126229859, CRYPT_GEN_RANDOM(1024))
    , (715871545, CRYPT_GEN_RANDOM(1024))
    , (-1163940131, CRYPT_GEN_RANDOM(1024))
    , (566332020, CRYPT_GEN_RANDOM(1024))
    , (1880249597, CRYPT_GEN_RANDOM(1024))
    , (-1213257849, CRYPT_GEN_RANDOM(1024))
    , (-155893134, CRYPT_GEN_RANDOM(1024))
    , (976883931, CRYPT_GEN_RANDOM(1024))
    , (-1424958821, CRYPT_GEN_RANDOM(1024))
    , (-279093766, CRYPT_GEN_RANDOM(1024))
    , (-903956376, CRYPT_GEN_RANDOM(1024))
    , (181119720, CRYPT_GEN_RANDOM(1024))
    , (-422397654, CRYPT_GEN_RANDOM(1024))
    , (-560438983, CRYPT_GEN_RANDOM(1024))
    , (968519165, CRYPT_GEN_RANDOM(1024))
    , (1820871210, CRYPT_GEN_RANDOM(1024))
    , (-1348787729, CRYPT_GEN_RANDOM(1024))
    , (-1869809700, CRYPT_GEN_RANDOM(1024))
    , (423340320, CRYPT_GEN_RANDOM(1024))
    , (125852107, CRYPT_GEN_RANDOM(1024))
    , (-1690550622, CRYPT_GEN_RANDOM(1024))
    , (570776311, CRYPT_GEN_RANDOM(1024))
    , (2120766755, CRYPT_GEN_RANDOM(1024))
    , (1123596784, CRYPT_GEN_RANDOM(1024))
    , (496886282, CRYPT_GEN_RANDOM(1024))
    , (-571192016, CRYPT_GEN_RANDOM(1024))
    , (1036877128, CRYPT_GEN_RANDOM(1024))
    , (1518056151, CRYPT_GEN_RANDOM(1024))
    , (1617326587, CRYPT_GEN_RANDOM(1024))
    , (410892484, CRYPT_GEN_RANDOM(1024))
    , (1826927956, CRYPT_GEN_RANDOM(1024))
    , (-1898916773, CRYPT_GEN_RANDOM(1024))
    , (245592851, CRYPT_GEN_RANDOM(1024))
    , (1826773413, CRYPT_GEN_RANDOM(1024))
    , (1451000899, CRYPT_GEN_RANDOM(1024))
    , (1234288293, CRYPT_GEN_RANDOM(1024))
    , (1433618321, CRYPT_GEN_RANDOM(1024))
    , (-1584291587, CRYPT_GEN_RANDOM(1024))
    , (-554159323, CRYPT_GEN_RANDOM(1024))
    , (-1478814392, CRYPT_GEN_RANDOM(1024))
    , (1326124163, CRYPT_GEN_RANDOM(1024))
    , (701812459, CRYPT_GEN_RANDOM(1024));
Run Code Online (Sandbox Code Playgroud)

第一列是主键,您可以看到值以随机(ish)顺序列出.以随机顺序列出值应该使SQL Server成为:

  1. 对数据进行排序,预插入
  2. 不对数据进行排序,导致表格碎片化.

CRYPT_GEN_RANDOM()函数用于每行生成1024字节的随机数据,以允许此表使用多个页面,从而使我们能够看到碎片插入的效果.

运行上面的插入后,您可以像这样检查碎片:

SELECT * 
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('TestSort'), 1, 0, 'SAMPLED') ips;
Run Code Online (Sandbox Code Playgroud)

在我的SQL Server 2012 Developer Edition实例上运行此操作会显示90%的平均碎片,表明SQL Server在插入期间没有排序.

这个特定故事的寓意可能是,"如果有疑问,可以分类,如果它会有益".话虽如此,ORDER BY在insert语句中添加和子句并不能保证插入按顺序发生.考虑插入并行时会发生什么,例如.

在非生产系统上,您可以使用跟踪标志2332作为insert语句的选项,以"强制"SQL Server在插入之前对输入进行排序. @PaulWhite有一篇有趣的文章,优化T-SQL查询,改变覆盖该数据的数据,以及其他细节.请注意,该跟踪标志不受支持,不应在生产系统中使用,因为这可能会使保修失效.在非生产系统中,对于您自己的教育,您可以尝试将其添加到INSERT语句的末尾:

OPTION (QUERYTRACEON 2332);
Run Code Online (Sandbox Code Playgroud)

将附加到插入后,查看计划,您将看到一个明确的排序:

在此输入图像描述

如果微软将这个作为支持的跟踪标志,那将是很好的.

保罗·怀特让我知道的SQL Server 不会自动引入一种运营商进入该计划时,它认为一个会有所帮助.对于上面的示例查询,如果我在values子句中运行带有250个项目的插入,则不会自动执行排序.但是,在251项中,SQL Server会在插入之前自动对值进行排序.为什么截止值是250/251行对我来说仍然是一个谜,除了它似乎是硬编码.如果我将SomeData列中插入的数据的大小减少到只有一个字节,则截止仍然是250/251行,即使两种情况下表的大小只是一个页面.有趣的是,查看插入SET STATISTICS IO, TIME ON;显示插入的单字节SomeData值在排序时需要两倍的时间.

没有排序(即插入250行):

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 16 ms, elapsed time = 16 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.
Table 'TestSort'. Scan count 0, logical reads 501, physical reads 0, 
   read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob 
   read-ahead reads 0.

(250 row(s) affected)

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 11 ms.

排序(即插入251行):

SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time: 
   CPU time = 15 ms, elapsed time = 17 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.
Table 'TestSort'. Scan count 0, logical reads 503, physical reads 0, 
   read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob 
   read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, 
   read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob 
   read-ahead reads 0.

(251 row(s) affected)

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 16 ms,  elapsed time = 21 ms.

一旦开始增加行大小,排序版本肯定会变得更有效.当插入4096个字节时SomeData,排序的插入在我的测试装备上几乎是未排序插入的两倍.


作为附注,如果您感兴趣,我VALUES (...)使用此T-SQL 生成了子句:

;WITH s AS (
    SELECT v.Item
    FROM (VALUES (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(Item)
)
, v AS (
    SELECT Num = CONVERT(int, CRYPT_GEN_RANDOM(10), 0)
)
, o AS (
    SELECT v.Num
        , rn = ROW_NUMBER() OVER (PARTITION BY v.Num ORDER BY NEWID())
    FROM s s1
        CROSS JOIN s s2
        CROSS JOIN s s3
        CROSS JOIN v 
)
SELECT TOP(50) ', (' 
        + REPLACE(CONVERT(varchar(11), o.Num), '*', '0') 
        + ', CRYPT_GEN_RANDOM(1024))'
FROM o
WHERE rn = 1
ORDER BY NEWID();
Run Code Online (Sandbox Code Playgroud)

这将生成1,000个随机值,仅选择第一列中具有唯一值的前50行.我将输出复制并粘贴到INSERT上面的语句中.