【问题标题】:Do gaps in the Identity (primary key clustered) table affects performance of database?Identity(主键聚集)表中的间隙是否会影响数据库的性能?
【发布时间】:2015-08-05 14:09:19
【问题描述】:

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

假设我有下表

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 万条记录 所以下一个identity id是2,000,001

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

所以重新排序表与娱乐会有所不同

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

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

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

谢谢

SQL Server 2014

【问题讨论】:

  • 不,不会对性能产生影响。由于这是您的集群键,无论种子是 45,322 还是 2,000,0001,记录仍将输入到记录 45,321 之后的集群索引上的下一个可用空间。 Identity 列的值是没有意义的,如果不是,您可能没有正确使用。在像这样的大删除之后,您可能最终会出现一些索引碎片,但身份种子与此完全无关。
  • @GarethD ty 回复所以在索引碎片整理之后他们应该有相同的性能吧?
  • 在索引碎片整理之前,它们将具有相同的性能。在删除表的大部分内容后,索引碎片整理只是一个需要考虑的问题。
  • @GarethD 所以在大删除之后我想 sql server 对所有剩余的记录重新排序,对吗?

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


【解决方案1】:

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

不会,不会对性能产生影响。由于这是您的集群键,无论种子是 45,322 还是 2,000,0001,记录仍将输入到记录 45,321 之后的集群索引上的下一个可用空间。 Identity 列的值是没有意义的,如果不是,您可能没有正确使用。在这样的大删除之后,您可能最终会出现一些索引碎片,但身份种子与此完全无关。

关于碎片,在一个非常简化的示例中,您可能有一个有 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

表“碎片整理测试”。扫描计数 1,逻辑读取 93 (重建前计数)

表“碎片整理测试”。扫描计数 1,逻辑读取 16 (重建后计数)

无种子输出:

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

表“碎片整理测试”。扫描计数 1,逻辑读取 93 (重建前计数)

表“碎片整理测试”。扫描计数 1,逻辑读取 16 (重建后计数)

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 小心不要混淆PRIMARY KEY 和CLUSTERED INDEX,虽然在这个问题的情况下它们是相同的,但记录物理保存方式的声明由数据库处理,因为这是主键不正确。因为它是集群键,它也恰好是主键,所以它被照顾了。这是一个重要的区别。
    猜你喜欢
    • 2014-04-08
    • 2013-09-09
    • 1970-01-01
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 2013-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多