更新 Azure SQL Server 中的行导致意外的页面拆分

And*_*all 7 azure-sql-database page-splits accelerated-database-recovery

我在使用 Azure SQL Server PAAS 的实时环境中遇到了很多我不理解的页面拆分。正在发生的更新不应增加行的大小,因此永远不会导致页面拆分。此外,该行为仅发生在 Azure 中,不会发生在本地 SQL 实例上。

我使用的是使用 eDTU 定价和标准层 (200 eDTU) 的 Azure 弹性池。

我创建了以下示例来演示:

create table dbo.TestSplit
(
    TestSplitId int not null identity,
    MyInt int not null,
    
    constraint PK_C_dbo_TestSplit_TestSplitId primary key clustered (TestSplitId)
);
Run Code Online (Sandbox Code Playgroud)

插入 100,000 行且 MyInt = 5:

insert into dbo.TestSplit
(MyInt)
select top(100000) 5
from sys.columns AS a 
cross join sys.columns AS b 
cross join sys.columns AS c
Run Code Online (Sandbox Code Playgroud)

运行以下 SQL 显示已创建 384 个页面,并且有 2 个碎片。

select
    ix.name as index_name,
    st.index_type_desc,
    st.fragment_count,
    st.page_count
from sys.dm_db_index_physical_stats(db_id(), NULL, NULL, NULL, NULL) st 
join sys.indexes ix on ix.object_id = st.object_id 
    and ix.index_id = st.index_id
where object_name(ix.object_id) = 'TestSplit'
and object_schema_name(ix.object_id) = 'dbo';
Run Code Online (Sandbox Code Playgroud)
索引名称 索引类型描述 片段计数 页数
PK_C_dbo_TestSplit_TestSplitId 聚集索引 2 第384章

现在每隔两行更新一次,将 MyInt 值从 5 更改为 6。

update dbo.TestSplit
set MyInt = 6
where TestSplitId %2 = 1
Run Code Online (Sandbox Code Playgroud)

MyInt 是一个固定宽度的列,因此我希望编辑只是更新页面而不会导致任何页面拆分。然而,页面数量大约增加了一倍,并且片段也遵循页面计数。

索引名称 索引类型描述 片段计数 页数
PK_C_dbo_TestSplit_TestSplitId 聚集索引 第767章 第767章

通过对诸如Wayne Sheffield - SQL Server 中的页面拆分之类的页面的建议,我可以看到页面拆分肯定是额外页面的原因。

我不明白为什么会发生这种情况或如何阻止它。

我已在本地尝试了所有五个事务隔离级别,但仍然没有页面拆分。我的本地 SQL 服务器是 2017 年 (v14.0.2037.2),我认为加速数据库恢复是在 2019 年推出的。我可以尝试升级本地服务器,但我当然仍然希望阻止实时 Azure 环境中发生的拆分。

Dan Guzman报告了与启用本地 SQL Server 2019 和 ADR 的 Azure SQL 数据库相同的拆分。

And*_*all 5

经过大量测试,我相信加速数据库恢复是原因。

编辑行时,加速数据库恢复将存储原始行和新行之间的差异。这称为行内版本控制,它允许快速回滚和恢复。

我的表示例是一个非常窄的表,存储 Int 值的差异基本上会使行的大小加倍。这就是为什么我们创建的页面数量几乎增加了一倍(请注意,这些页面也几乎已满)。

Alex Orpwood 已经发现了这个问题并联系了微软。他们回复说这是预期的行为,您可以使用跟踪标志禁用此功能,但他们不推荐它,而且我找不到该标志。

在Azure PAAS SQL Server中,您无法禁用ADR,因此看起来我陷入了这种行为。我只需要在表上使用通常不需要的填充因子,并承受数据库将比本地数据库更大的打击,正如Dan Guzman提到的:

填充因子为 80 将页数减少到 527 ( CREATE UNIQUE CLUSTERED INDEX PK_C_dbo_TestSplit_TestSplitId ON dbo.TestSplit(TestSplitId) WITH(DROP_EXISTING=ON,FILLFACTOR=80);)。