好吧,百万美元的问题来了
假设我有下表
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 万条记录
因此下一个身份ID
是2,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/