Identity (primary key clustered) table 中的差距是否会影响数据库的性能?

Do gaps in the Identity (primary key clustered) table affects performance of database?

好吧,百万美元的问题来了

假设我有以下 table

SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

SET ANSI_PADDING ON
GO

CREATE TABLE [dbo].[tblUsersProfile](
    [personId] [int] IDENTITY(1,1) NOT NULL,
    [personName] [varchar](16) NOT NULL,
    [personSurName] [varchar](16) NOT NULL,
 CONSTRAINT [PK_tblUsersProfile] PRIMARY KEY CLUSTERED 
(
    [personId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 90) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

现在假设这个 table 有 200 万条记录 所以下一个identity id2,000,001

但是我正在批量删除并且记录数变为45,321 然而下一个 identity id 将是 2,000,001 仍然是

因此重新排序 table 与娱乐会有任何不同

在第一种情况下,将有 45,321 条记录,但身份 ID 将为 2,000,001

在第二种情况下,将再次有 45,321 条记录,但身份 ID 将为 45,322

那么这两种情况在性能、存储等方面会有什么不同吗?

谢谢

SQL 服务器 2014

没有。由于它是一个主键数据库引擎,因此将负责这些记录的物理保存方式。

扩展我的评论以进一步解释。为清楚起见,评论是:

No, there will be no impact on performance. Since this is your clustering key, whether or not the seed is 45,322, or 2,000,0001, the record will still be entered onto the next free space on the clustered index after the record 45,321. The value of an Identity column is intended to be meaningless, if it is not you are probably not using correctly. After a big delete like that you may end up with some index fragmentation, but the identity seed is completely unrelated to this.

关于碎片,在一个非常简化的示例中,您可能有一个 table 5 页,每页 100 条记录:

  • 第 1 页(ID 1 - 100)
  • 第 2 页(ID 101 - 200)
  • 第 3 页(ID 201 - 300)
  • 第 4 页(ID 301 - 400)
  • 第 5 页(ID 401 - 500)

现在,如果您执行删除操作,并删除最后一位数字不为 1 的所有记录,以及 ID 超过 300 的所有记录,您将得到:

  • 第 1 页(ID 1、11、21、31、41、51、61、71、81、91)
  • 第 2 页(ID 11、111、121、131、141、151、161、171、181、191)
  • 第 3 页(ID 21、211、221、231、241、251、261、271、281、291)
  • 第 4 页(空)
  • 第 5 页(空)

当我们现在插入这个 table 时,无论下一个身份是 291 还是 501,它都不会改变任何东西。页面必须保持正确的顺序,所以最高ID是291,所以必须在之后插入下一条记录,如果有space则在同一页,否则创建一个新页面。在这种情况下,第 3 页上有 9 个空位,因此可以在此处插入下一条记录。由于 292 和 500 都高于 291,因此行为相同。

在这两种情况下,问题仍然存在,删除后您有 3 页有很多空闲 space(仅 10% 已满),您现在只有 30 条记录,很适合放在一页上,所以你可以重建你的索引来做到这一点,这样你现在只需要阅读一个页面就可以获取所有数据。

再次强调,这是一个非常简单的示例,我不建议重建聚簇索引来释放 2 个页面!

同样重要的是要强调此行为是因为 ID 列是聚簇键,而不是主键。它们不一定是相同的,但是,如果您在身份列以外的其他东西上进行集群,那么无论您是否在删除后重新播种,它仍然不会对性能产生影响。标识列纯粹是为了标识,只要您可以唯一标识一行,实际值是无关紧要的。


示例测试代码

-- CREATE TABLE AND FILL WITH 100,000 ROWS
IF OBJECT_ID(N'dbo.DefragTest', 'U') IS NOT NULL
    DROP TABLE dbo.DefragTest;

CREATE TABLE dbo.DefragTest (ID INT IDENTITY(1, 1) PRIMARY KEY, Filler CHAR(1) NULL);
INSERT dbo.DefragTest (Filler)
SELECT TOP 100000 NULL
FROM sys.all_objects AS a, sys.all_objects AS b;


-- CHECK PAGE STATISTICS
SELECT  Stage = 'After Initial Insert',
        IdentitySeed = IDENT_CURRENT(N'dbo.DefragTest'),
        p.rows, 
        a.total_pages,
        a.data_pages,
        AvgRecordsPerPage = CAST(p.rows / CAST(a.data_pages AS FLOAT) AS DECIMAL(10, 2))
FROM    sys.partitions AS p
        LEFT JOIN sys.allocation_units AS a
            ON a.container_id = p.partition_id
WHERE   p.[object_id] = OBJECT_ID(N'dbo.DefragTest', 'U')
AND     p.index_id IN (0, 1); -- CLUSTERED OR HEAP

-- DELETE RECORDS
DELETE  dbo.DefragTest
WHERE   ID % 10 != 1
OR      ID > 50000;

-- CHECK PAGE STATISTICS
SELECT  Stage = 'After Delete',
        IdentitySeed = IDENT_CURRENT(N'dbo.DefragTest'),
        p.rows, 
        a.total_pages,
        a.data_pages,
        AvgRecordsPerPage = CAST(p.rows / CAST(a.data_pages AS FLOAT) AS DECIMAL(10, 2))
FROM    sys.partitions AS p
        LEFT JOIN sys.allocation_units AS a
            ON a.container_id = p.partition_id
WHERE   p.[object_id] = OBJECT_ID(N'dbo.DefragTest', 'U')
AND     p.index_id IN (0, 1); -- CLUSTERED OR HEAP  

-- RESEED (REMOVED FOR ONE RUN)
DBCC CHECKIDENT ('dbo.DefragTest', RESEED, 50000);

--INSERT ROWS TO SEE EFFECT ON PAGE
INSERT dbo.DefragTest (Filler)
SELECT TOP 10000 NULL
FROM sys.all_objects AS a;

-- CHECK PAGE STATISTICS
SELECT  Stage = 'After Second Insert',
        IdentitySeed = IDENT_CURRENT(N'dbo.DefragTest'),
        p.rows, 
        a.total_pages,
        a.data_pages,
        AvgRecordsPerPage = CAST(p.rows / CAST(a.data_pages AS FLOAT) AS DECIMAL(10, 2))
FROM    sys.partitions AS p
        LEFT JOIN sys.allocation_units AS a
            ON a.container_id = p.partition_id
WHERE   p.[object_id] = OBJECT_ID(N'dbo.DefragTest', 'U')
AND     p.index_id IN (0, 1); -- CLUSTERED OR HEAP  

-- CHECK READS REQUIRED FOR FULL TABLE SCAN
SET STATISTICS IO ON;
SELECT COUNT(Filler)
FROM dbo.DefragTest;

-- REBUILD INDEX
ALTER INDEX PK_DefragTest__ID ON dbo.DefragTest REBUILD;

-- CHECK PAGE STATISTICS
SELECT  Stage = 'After Index Rebuild',
        IdentitySeed = IDENT_CURRENT(N'dbo.DefragTest'),
        p.rows, 
        a.total_pages,
        a.data_pages,
        AvgRecordsPerPage = CAST(p.rows / CAST(a.data_pages AS FLOAT) AS DECIMAL(10, 2))
FROM    sys.partitions AS p
        LEFT JOIN sys.allocation_units AS a
            ON a.container_id = p.partition_id
WHERE   p.[object_id] = OBJECT_ID(N'dbo.DefragTest', 'U')
AND     p.index_id IN (0, 1); -- CLUSTERED OR HEAP  

-- CHECK READS REQUIRED FOR FULL TABLE SCAN

SELECT COUNT(Filler)
FROM dbo.DefragTest;

SET STATISTICS IO OFF;  

带种子的输出:

Stage                   IdentitySeed    rows    total_pages     data_pages  AvgRecordsPerPage
After Initial Insert    100000          100000  178             174         574.71
After Delete            100000          5000    90              87          57.47
After Second Insert     52624           7624    98              91          83.78
After Index Rebuild     52624           7624    18              14          544.57

Table 'DefragTest'. Scan count 1, logical reads 93 (Count before rebuild)

Table 'DefragTest'. Scan count 1, logical reads 16 (Count after rebuild)

没有种子的输出:

Stage                   IdentitySeed    rows    total_pages     data_pages  AvgRecordsPerPage
After Initial Insert    100000          100000  178             174         574.71
After Delete            100000          5000    90              87          57.47
After Second Insert     102624          7624    98              91          83.78
After Index Rebuild     52624           7624    18              14          544.57

Table 'DefragTest'. Scan count 1, logical reads 93 (Count before rebuild)

Table 'DefragTest'. Scan count 1, logical reads 16 (Count after rebuild)

如您所见,在每种情况下都没有区别,在存储或读取数据的方式上,只有 IDENT_INCR() 的值发生变化,并且在这两种情况下都重建聚集索引大大减少了页面数量,这反过来又提高了查询性能,因为获得相同数据量的逻辑读取更少。