页拆分在update时增多是因为修改可变长度列致记录变长且页无剩余空间,触发页分裂;fillfactor仅影响索引创建/重建,不约束后续update;需结合更新模式设定(如oltp常用70–85),并辅以字段分离、varchar(max)、sparse列等治本手段。

页拆分为什么在UPDATE时突然变多?
SQL Server 的页拆分通常不是发生在 INSERT 时,而是 UPDATE 修改了可变长度列(如 varchar、text、xml)且新值比原值长时触发的。此时数据页已满,又没空间容纳扩展后的记录,引擎只能把页一分为二——这就是“页拆分”。它不光消耗 I/O,还会导致索引碎片飙升、查询变慢。
关键点在于:即使你设了 FILLFACTOR,它只影响索引创建或重建时的初始填充,对后续 UPDATE 无直接约束;而页内剩余空间(free_space_in_bytes)才是决定是否拆分的实时依据。
填充因子(FILLFACTOR)到底该设多少?
设太高(比如 100)会让页很快填满,UPDATE 时极易触发拆分;设太低(比如 50)又浪费磁盘和内存,降低缓存效率。没有通用值,得看实际更新模式:
- 如果表以追加为主、极少更新长度(如日志表),
FILLFACTOR = 100完全合理 - 如果频繁更新
varchar(500)字段,且平均增长 20–30 字节,建议从FILLFACTOR = 80开始测试 - 对高并发 OLTP 表,
FILLFACTOR = 70–85是较常见的折中起点
注意:FILLFACTOR 只作用于聚集索引(或堆的非叶级),对非聚集索引也生效,但不会自动传播到已存在的页——必须通过 ALTER INDEX ... REBUILD 才能应用。
如何验证当前页拆分是否真由UPDATE引发?
别只看 sys.dm_db_index_physical_stats 的 avg_page_space_used_in_percent,它反映的是静态填充率,掩盖了动态过程。真正要抓的是运行时的页拆分事件:
- 启用 SQL Server Profiler 或 XEvent,捕获
Page Split事件,过滤DatabaseName和ObjectName - 检查
sys.dm_db_index_operational_stats中的page_splits累计值,对比两次快照差值 - 重点观察
leaf_insert_count和leaf_update_count的比例:若后者远高于前者,基本可锁定是 UPDATE 导致
示例查当前拆分次数:
SELECT OBJECT_NAME(object_id) AS table_name, index_id, page_splits FROM sys.dm_db_index_operational_stats(DB_ID(), NULL, NULL, NULL) WHERE page_splits > 0;
除了FILLFACTOR,还有哪些更直接的缓解手段?
调 FILLFACTOR 是治标,真正治本得从数据结构和访问模式入手:
- 把经常变长的字段(如备注、JSON 内容)移到单独的宽表或
FILESTREAM列中,主表保持窄而稳定 - 用
varchar(max)替代固定大varchar(n),让超出 8060 字节的部分自动溢出到行外存储,避免页内膨胀 - 对高频更新字段,考虑用
SPARSE列(尤其当多数行为空值时),节省空间并减少移动需求 - 定期执行
ALTER INDEX ... REORGANIZE(比 REBUILD 轻量,不锁表),它会合并半空页,但不会按FILLFACTOR重排
最常被忽略的一点:页拆分压力往往集中在少数热点页(比如最新订单的 status 字段),这时分区或哈希拆分比全局调 FILLFACTOR 更有效——但代价是查询逻辑变复杂。










