sql-server - Identity(主键聚集)表中的间隙是否会影响数据库的性能?

标签 sql-server database-design database-performance sql-server-2014 database-tuning

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

假设我有下表

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

现在假设这个表有 200 万条记录 因此下一个身份ID2,000,001

但是我正在批量删除,记录数变为 45,321 但是下一个身份ID仍将是2,000,001

因此,通过娱乐重新排序表格会有所不同

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

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

那么这两种情况之间会有性能、存储等差异吗?

谢谢

SQL Server 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.

关于碎片,在一个非常简单的示例中,您可能有一个包含 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 页(空)

当我们现在向该表插入数据时,无论下一个标识是 291 还是 501,都不会改变任何内容。页必须保持正确的顺序,因此最高 ID 是 291,因此必须在之后插入下一条记录,如果有空间则在同一页上,否则将创建一个新页。在这种情况下,第 3 页上有 9 个空槽,因此可以在那里插入下一条记录。由于 292 和 500 都高于 291,因此行为是相同的。

在这两种情况下,问题仍然是,删除后,您有 3 个页面,其中有大量可用空间(仅占 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() 的值发生了变化,并且在两种情况下都会重建聚集索引大大减少了页数,从而提高了查询性能,因为获得相同数据量的逻辑读取次数更少。

关于sql-server - Identity(主键聚集)表中的间隙是否会影响数据库的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31834943/

相关文章:

sql - SQL Server 2008 中的大小写和计数

sql - While 循环遍历数据库

database-design - 我们可以在MS Access数据库上创建多列唯一索引吗?

database-design - ER 建模问题

sql - 数据库结构反馈

mysql - sql查询执行时间很长

SQL Server 使用正则表达式选择

sql - 在Sql Server中,如何将二进制字符串转换为二进制?

php - MySQL 存储过程和准备语句的性能比较

ios - 核心数据 - 以多对多关系访问实例与获取请求?