Identity(主键聚簇)表中的间隙会影响数据库的性能吗?

时间:2015-08-05 14:09:19

标签: 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万条记录 所以下一个identity id2,000,001

然而,我正在进行大规模删除,记录数量变为45,321 但是,下一个identity id仍然是2,000,001

所以重新排序表与娱乐会有任何区别

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

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

这两种情况之间会有任何性能,存储等差异吗?

谢谢

SQL Server 2014

2 个答案:

答案 0 :(得分:4)

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

  

不,对性能没有影响。由于这是您的聚类键,无论种子是45,322还是2,000,0001,记录仍将在记录45,321之后输入到聚簇索引上的下一个可用空间。 Identity列的值意图毫无意义,如果不是,您可能没有正确使用。经过一次大的删除后,你可能会得到一些索引碎片,但身份种子与此完全无关。

关于碎片,在一个非常简单的例子中,你可能有一个包含5页的表,每页有100条记录:

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

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

  • Page 1(身份证号1,11,21,31,41,51,61,71,81,91)
  • Page 2(身份证号11,111,121,131,141,151,161,171,181,191)
  • Page 3(身份证号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;  

使用Reseed输出:

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
  

表'DefragTest'。扫描计数1,逻辑读取93 (重建前计数)

     

表'DefragTest'。扫描计数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
  

表'DefragTest'。扫描计数1,逻辑读取93 (重建前计数)

     

表'DefragTest'。扫描计数1,逻辑读取16 (重建后计数)

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

答案 1 :(得分:1)

没有。因为它是一个主键数据库引擎,所以会照顾这些记录的物理保存方式。