Identity (primary key clustered) table 中的差距是否会影响数据库的性能?
Do gaps in the Identity (primary key clustered) table affects performance of database?
好吧,百万美元的问题来了
假设我有以下 table
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
现在假设这个 table 有 200 万条记录
所以下一个identity id
是2,000,001
但是我正在批量删除并且记录数变为45,321
然而下一个 identity id
将是 2,000,001
仍然是
因此重新排序 table 与娱乐会有任何不同
在第一种情况下,将有 45,321 条记录,但身份 ID 将为 2,000,001
在第二种情况下,将再次有 45,321 条记录,但身份 ID 将为 45,322
那么这两种情况在性能、存储等方面会有什么不同吗?
谢谢
SQL 服务器 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.
关于碎片,在一个非常简化的示例中,您可能有一个 table 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 页(空)
当我们现在插入这个 table 时,无论下一个身份是 291 还是 501,它都不会改变任何东西。页面必须保持正确的顺序,所以最高ID是291,所以必须在之后插入下一条记录,如果有space则在同一页,否则创建一个新页面。在这种情况下,第 3 页上有 9 个空位,因此可以在此处插入下一条记录。由于 292 和 500 都高于 291,因此行为相同。
在这两种情况下,问题仍然存在,删除后您有 3 页有很多空闲 space(仅 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()
的值发生变化,并且在这两种情况下都重建聚集索引大大减少了页面数量,这反过来又提高了查询性能,因为获得相同数据量的逻辑读取更少。
好吧,百万美元的问题来了
假设我有以下 table
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
现在假设这个 table 有 200 万条记录
所以下一个identity id
是2,000,001
但是我正在批量删除并且记录数变为45,321
然而下一个 identity id
将是 2,000,001
仍然是
因此重新排序 table 与娱乐会有任何不同
在第一种情况下,将有 45,321 条记录,但身份 ID 将为 2,000,001
在第二种情况下,将再次有 45,321 条记录,但身份 ID 将为 45,322
那么这两种情况在性能、存储等方面会有什么不同吗?
谢谢
SQL 服务器 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.
关于碎片,在一个非常简化的示例中,您可能有一个 table 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 页(空)
当我们现在插入这个 table 时,无论下一个身份是 291 还是 501,它都不会改变任何东西。页面必须保持正确的顺序,所以最高ID是291,所以必须在之后插入下一条记录,如果有space则在同一页,否则创建一个新页面。在这种情况下,第 3 页上有 9 个空位,因此可以在此处插入下一条记录。由于 292 和 500 都高于 291,因此行为相同。
在这两种情况下,问题仍然存在,删除后您有 3 页有很多空闲 space(仅 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()
的值发生变化,并且在这两种情况下都重建聚集索引大大减少了页面数量,这反过来又提高了查询性能,因为获得相同数据量的逻辑读取更少。