UPDATE触发页拆分本质是行变长且页无足够空闲空间,非整行移动;需优先分离大字段、用varchar(max)或SPARSE列,并删除冗余索引。

批量 UPDATE 时行数据跨页移动,本质是页拆分(Page Split),不是“移动”而是“分裂”。SQL Server 不会把整行从一页搬到另一页,而是当某行因更新变长、当前页没空间容纳时,将该页一分为二——原页留部分行,新页放其余行。真正要防的不是移动,是拆分。
为什么 UPDATE 会触发页拆分?
核心原因只有两个:行长度增加 + 当前页无足够空闲空间。常见于更新 varchar、text、xml 或 varchar(max) 字段,且新值比旧值长几十字节以上。此时即使 FILLFACTOR = 80,只要页内剩余字节数
-
FILLFACTOR只在CREATE INDEX或ALTER INDEX ... REBUILD时生效,对已有页不重排,也不约束后续任何一次UPDATE - 页内真实可用空间由
sys.dm_db_index_physical_stats的free_space_in_bytes决定,不是百分比估算 - 聚集索引键变更(如改主键值)也会强制行迁移,但这是
KEY更新,不是普通字段更新
怎么确认是不是 UPDATE 在引发页拆分?
别信平均填充率,查运行时指标:
- 执行
SELECT OBJECT_NAME(object_id), index_id, page_splits FROM sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL) WHERE page_splits > 0 - 对比两次快照的
page_splits差值 ÷ 同期leaf_update_count,比值 > 0.1 就说明UPDATE是主力 - 用 XEvent 捕获
Page Split事件,过滤出具体表名和索引名,直击源头
比调 FILLFACTOR 更有效的三件事
调填充因子是缓兵之计,治标不治本:
- 把常变长字段(如
content、memo、JSON blob)移到单独宽表,主表保持窄而稳定 - 对超长文本改用
varchar(max)+TEXTIMAGE_ON文件组,或启用SPARSE列(空值不占空间) - 删掉冗余二级索引——每次
UPDATE都要同步更新所有相关 B+ 树,索引越多,页分裂压力越大
真正难处理的是那种字段:定义为 varchar(2000),但业务上经常从 50 字节涨到 800 字节。这时候再怎么调 FILLFACTOR 都没用,因为页内剩余空间根本扛不住——结构改造比参数调整更直接有效。

















