2

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

假设我有下表

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 条记录,但身份 ID 将为 2,000,001

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

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

谢谢

SQL Server 2014

4

2 回答 2

4

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

不,不会对性能产生影响。由于这是您的集群键,无论种子是 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(),变化的只是值,并且在这两种情况下,重建聚集索引都大大减少了页面数量,这反过来又提高查询性能,因为获得相同数据量的逻辑读取更少。

于 2015-08-05T14:48:30.530 回答
1

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

于 2015-08-05T14:14:00.080 回答