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

时间:2016-12-23 10:12:39

标签: sql sql-server sql-server-2008 indexing clustered-index

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

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

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

请注意,问题与INSERT表现有关,而不是SELECT

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

4 个答案:

答案 0 :(得分:7)

有区别。插入群集顺序会导致大量碎片。

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

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)

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

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) 

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

这个例子最引人注目的是DESC聚集索引的碎片超过99%。 This is recreating the same bad behaviour as using a random GUID for a clustered index. 以下代码演示了碎片。

re.sub()

更新:

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

<强>更新

当SQL可以执行Backward Scans时,出现了一个关于降序索引的重点的讨论。请阅读有关limitations of Backward Scans的文章。

答案 1 :(得分: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));

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

  1. 对数据进行排序,预插入
  2. 不对数据进行排序,导致表格碎片化。
  3. CRYPT_GEN_RANDOM()函数用于每行生成1024字节的随机数据,以允许此表使用多个页面,从而使我们能够看到碎片插入的效果。

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

    SELECT * 
    FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID('TestSort'), 1, 0, 'SAMPLED') ips;
    

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

    这个特定故事的寓意很可能是,&#34;如果有疑问,排序,如果这将是有益的&#34;。话虽如此,在insert语句中添加ORDER BY子句并不能保证插入按顺序发生。考虑插入并行时会发生什么,例如。

    在非生产系统上,您可以使用跟踪标志2332作为插入语句的选项,以强制&#34;强制&#34; SQL Server在插入输入之前对输入进行排序。 @PaulWhite有一篇有趣的文章,Optimizing T-SQL queries that change data涵盖了该文章和其他细节。请注意,该跟踪标志不受支持,不应在生产系统中使用,因为这可能会使保修失效。在非生产系统中,对于您自己的教育,您可以尝试将其添加到INSERT语句的末尾:

    OPTION (QUERYTRACEON 2332);
    

    如果您已将附加内容添加到插入内容中,请查看该计划,您将看到一个明确的类别:

    enter image description here

    如果微软能够将其作为支持的跟踪标志,那就太好了。

    SQL Server

    Paul White made me aware会在计划认为其有用时自动将排序运算符引入计划中。对于上面的示例查询,如果我在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.
    

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

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

    ;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();
    

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

答案 2 :(得分:0)

只要数据按聚集索引排序(无论是上升还是下降),就不会对插入性能产生任何影响。这背后的原因是SQL不关心聚集索引的页面中行的物理顺序。行的顺序保存在所谓的“记录偏移数组”中,这是唯一一个需要为新行重写的数据(无论顺序如何,无论如何都会这样做)。实际的数据行将一个接一个地写入。

在事务日志级别,条目应该是相同的,与方向无关,因此不会对性能产生任何额外影响。通常,事务日志是产生大多数性能问题的日志,但在这种情况下将没有。

您可以在https://www.simple-talk.com/sql/database-administration/sql-server-storage-internals-101/找到关于页面/行的物理结构的详细说明。

所以基本上只要你的插入不会生成页面拆分(如果数据按聚集索引的顺序排列而不管顺序如何),如果对插入性能有任何影响,你的插入将是微不足道的。

答案 3 :(得分:0)

基于以下代码,当所选数据按排序聚簇索引的相反方向排序时,将数据插入到具有已排序聚簇索引的标识列中会更加资源紧张。

在此示例中,逻辑读取几乎是两倍。

10次运行后,排序的升序逻辑读取平均值为2284,排序的降序逻辑读取值平均值为4301.

--Drop Table Destination;
Create Table Destination (MyId INT IDENTITY(1,1))

Create Clustered Index ClIndex On Destination(MyId ASC)

set identity_insert destination on 
Insert into Destination (MyId)
SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
FROM sys.all_objects 
ORDER BY n


set identity_insert destination on 
Insert into Destination (MyId)
SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) 
FROM sys.all_objects 
ORDER BY n desc;

如果您感兴趣,请参阅逻辑读取的更多信息: https://www.brentozar.com/archive/2012/06/tsql-measure-performance-improvements/