将身份列重新播种回 0 会导致页面拆分吗?

Kra*_*atz 1 performance sql-server clustered-index sql-server-2014

以身份表作为聚簇主键的简单表。我们意外地达到了该列的最大值,因此我要求我们的 DBA 将表重新播种回 0,以便给我们一些时间来检查更新该列。该表也根据时间被清除,所以在我们与任何东西发生碰撞之前,我们有一个很大的 Id 缺口需要用完。由于这是一个高插入表,DBA 提出了页面拆分的可能性,因为我们不再在表的末尾插入。

我的问题是,当标识列的值环绕时,SQL Server 将如何表现?插入间隙会导致性能问题吗?我想考虑创建一个自动循环的序列,而不是将 id 列上升到 bigint。

Dav*_*oft 6

我的问题是,当标识列的值环绕时,SQL Server 将如何表现?

假设索引是 100% 满的,在新键值处的前几次插入将导致一到两页拆分,其中将整页上的一半行复制到新页,然后将新行插入其中之一半整页。

但是,在之后您将恢复低成本插入和页面分配。当页面填满时,会分配一个新的空页面并将其插入到叶页面的双向链表中。没有行被复制到新页,新行被写入新页。

只有这一点,并结束指数的刀片之间的差异是存在下一个页面的新页之后。并且必须使用指向新页面的反向页面指针更新下一页。

有关详细信息,请参阅此帖子:Good Page Splits and Sequential GUID Key Generation

插入间隙会导致性能问题吗?

不。

当 SQL 继续插入索引的“中间”时,当插入的目标页面已满时,SQL 将始终分配一个新页面。它不会将该行放在下一个现有页面上,即使该页面未满。聚集索引的非叶级存储每个叶级页的起始值,因此 SQL 无法在不重写索引的非叶级的情况下将新行插入到下一个现有页中。

您可以通过强制页面锁定并查看插入的锁定占用空间(或使用 DBCC PAGE 不太容易)来轻松查看此行为。在下面的示例中,可以在页面上恰好容纳 4 行的聚集索引表进行了一系列中间索引插入。

假设每页可以容纳 4 行。您有整页现有行,键值从 10-20 升序,现在您要插入键值 1-9。

use master
drop database ps_test
go
create database ps_test
go
use ps_test 
go
drop table if exists ps_test
set nocount on


go

--8,060=4*(4+2011)
create table ps_test(id int primary key, data char(2011) not null default '')

go
DBCC TRACEON(3604) 
go

insert into ps_test(id) values (10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20),(21)--,22

go

dbcc ind('ps_test','ps_test',1)
--three data pages
/*

PageFID PagePID         PageType
------- ------- / /     --------
1       336             10      
1       328             1       
1       329             2       
1       330             1       
1       331             1       
*/


insert into ps_test(id) values (1)

dbcc ind('ps_test','ps_test',1)
--four data pages 332 has been inserted between 328 and 330
/*

PageFID PagePID        PageType PrevPagePID NextPagePID 
------- ---------/ /   -------- ----------- ----------- 
1       336            10       0           0           
1       328            1        0           332         
1       329            2        0           0           
1       330            1        332         331         
1       331            1        330         0           
1       332            1        328         330         
*/

dbcc page(ps_test,1,328,3)
-- 328 has rows 1,10 and 4048 bytes free
-- the page is split
/*
PAGE: (1:328)
...
pminlen = 2019                      m_slotCnt = 2                       m_freeCnt = 4048
...
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 1                              
...
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 10                             

DBCC execution completed. If DBCC printed error messages, contact your system administrator.
*/

dbcc page(ps_test,1,332,3)
--page 332 has 11,12,13 and 2024 bytes free
/*

PAGE: (1:332)
...
pminlen = 2019                      m_slotCnt = 3                       m_freeCnt = 2024
...
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 11                             
...
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 12                             
...
Slot 2 Column 1 Offset 0x4 Length 4 Length (physical) 4
id = 13                             
...
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
*/

insert into ps_test(id) values (2)
insert into ps_test(id) values (3)

dbcc ind('ps_test','ps_test',1)
--no page split.  
/*

PageFID PagePID     PageType IndexLevel NextPageFID NextPagePID PrevPageFID PrevPagePID
------- ----------- -------- ---------- ----------- ----------- ----------- -----------
1       336         10       NULL       0           0           0           0
1       328         1        0          1           332         0           0
1       329         2        1          0           0           0           0
1       330         1        0          1           331         1           332
1       331         1        0          0           0           1           330
1       332         1        0          1           330         1           328

the new rows went on page 328, which now has

pminlen = 2019                      m_slotCnt = 4                       m_freeCnt = 2024
*/
Run Code Online (Sandbox Code Playgroud)

所以现在我们有第 328 页已满,下一页 332 只有 3 行。但是332上的最低值是11,所以当我们插入值4的时候,会不会继续332,插入5会导致另一个坏页分裂?

insert into ps_test(id) values (4)
dbcc ind('ps_test','ps_test',1)

/*
A new page 333 has been inserted in the list between 328 and 332

PageFID PagePID     PageType IndexLevel NextPageFID NextPagePID PrevPageFID PrevPagePID
------- ----------- -------- ---------- ----------- ----------- ----------- -----------
1       336         10       NULL       0           0           0           0
1       328         1        0          1           333         0           0
1       329         2        1          0           0           0           0
1       330         1        0          1           331         1           332
1       331         1        0          0           0           1           330
1       332         1        0          1           330         1           333
1       333         1        0          1           332         1           328

*/

dbcc page(ps_test,1,333,3)

/*

PAGE: (1:333)
...
pminlen = 2019                      m_slotCnt = 1                       m_freeCnt = 6072
...
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 10                             
...
DBCC execution completed. If DBCC printed error messages, contact your system administrator.

*/
Run Code Online (Sandbox Code Playgroud)

所以另一个“坏页拆分”,但这个只移动了 id=10 的行。并留下第 328 页完整的值 1,2,3,4

dbcc page(ps_test,1,328,3)
/*
PAGE: (1:328)
...
pminlen = 2019                      m_slotCnt = 4                       m_freeCnt = 0
...
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 1  
...
Slot 1 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 2 
...
Slot 2 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 3  
...
Slot 3 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 4                              
...                        
DBCC execution completed. If DBCC printed error messages, contact your system administrator.

*/
Run Code Online (Sandbox Code Playgroud)

现在所有较大的键值都在单独的页面上,从这里开始,不再有糟糕的页面拆分。下一次插入需要一个新页,并且只有新行在新页上:

    insert into ps_test(id) values (5)
    dbcc ind('ps_test','ps_test',1)

/*

PageFID PagePID      PageType IndexLevel NextPageFID NextPagePID PrevPageFID PrevPagePID
------- -----------  -------- ---------- ----------- ----------- ----------- -----------
1       336          10       NULL       0           0           0           0
1       328          1        0          1           334         0           0
1       329          2        1          0           0           0           0
1       330          1        0          1           331         1           332
1       331          1        0          0           0           1           330
1       332          1        0          1           330         1           333
1       333          1        0          1           332         1           334
1       334          1        0          1           333         1           328

*/
Run Code Online (Sandbox Code Playgroud)

新页面 334,只有最后插入的行。

 dbcc page(ps_test,1,334,3)
/*
PAGE: (1:334)
...
pminlen = 2019                      m_slotCnt = 1                       m_freeCnt = 6072
...
Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4

id = 5     
...
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
id = 5                              


*/
Run Code Online (Sandbox Code Playgroud)

因此,在需要移动行的几个初始“坏页拆分”之后,随后新页被分配并拼接到叶页的双向链接列表的中间。当新行不适合前一页并且没有行迁移到新页时,将分配新页。

  • 不久前,我写了一篇与此类似的博客文章(将身份转换为负值)。我建议看一看,不是因为它增加了很多新信息,而是我做了一些关于你可能期望发生的事情以及实际发生的事情的很好的视觉效果。https://sqlstudies.com/2016/09/21/a-quick-fix-for-a-full-identity-column/ (2认同)