批量UPDATE后必须手动更新统计信息,否则查询计划会因基数估计错误而走错索引;SQL Server用UPDATE STATISTICS,PostgreSQL用ANALYZE,MySQL需ANALYZE TABLE,且不可依赖重建索引替代。

UPDATE 后统计信息没更新,查询计划还是走错路
SQL Server 和 PostgreSQL 都会在批量 UPDATE 后“假装”数据分布没变,尤其是当修改行数超过阈值(SQL Server 默认是 20% + 500 行,PostgreSQL 是 autovacuum_analyze_scale_factor 控制)但自动分析没触发时。结果就是执行计划缓存了旧的基数估计,WHERE 条件走错索引,甚至全表扫描。
实操建议:
- SQL Server:立刻跑
UPDATE STATISTICS,别等自动更新——尤其在 ETL 脚本末尾加一句UPDATE STATISTICS dbo.TableName WITH FULLSCAN;如果表大,用SAMPLE 30 PERCENT平衡速度和精度 - PostgreSQL:手动触发
ANALYZE TableName;若刚做完百万级更新,加ANALYZE TableName (col1, col2)指定关键列,比全表快且更准 - MySQL(InnoDB):没有自动统计更新延迟问题,但
UPDATE不会刷新information_schema.STATISTICS中的 Cardinality;需显式执行ANALYZE TABLE TableName
重建索引 vs 更新统计信息,别混成一件事
重建索引(ALTER INDEX ... REBUILD 或 REINDEX)会顺带更新统计信息,但代价高、锁表久;而单纯更新统计信息(UPDATE STATISTICS / ANALYZE)毫秒级、几乎无锁。很多人图省事 rebuild 一把梭,结果阻塞业务写入十几分钟。
实操建议:
- 只改了数据内容、没动索引结构?跳过重建,直接更新统计信息
- 索引碎片率
> 30%(查sys.dm_db_index_physical_stats)且同时要更新统计?再考虑 rebuild - PostgreSQL 中
VACUUM ANALYZE比单独ANALYZE多做清理,但耗时翻倍;日常批量更新后,ANALYZE足够
WHERE 条件列没统计信息,优化器直接“瞎猜”
默认统计信息只建在索引键列或 CREATE STATISTICS 显式指定的列上。如果 UPDATE 后常按 status = 'processed' 查询,但 status 不在任何索引里,SQL Server/PG 就不会为它维护直方图,优化器只能按均匀分布估算——导致本该走索引的查询被判定为“查太多”,弃用索引。
实操建议:
- SQL Server:用
CREATE STATISTICS stat_status ON TableName (status)补单列统计;避免用WITH FULLSCAN,采样即可 - PostgreSQL:不需要手动建统计对象,但确保
ANALYZE时包含该列——ANALYZE TableName (status) - MySQL:对高频过滤列建单列索引(哪怕不用于 JOIN),因为 InnoDB 的统计依赖索引存在
自动化脚本里漏掉统计信息更新,上线就慢
很多 DBA 在部署脚本里写了 UPDATE、写了 REINDEX,却忘了最后一行 ANALYZE 或 UPDATE STATISTICS。结果凌晨跑完批量任务,早上第一波查询就超时——不是 SQL 写得差,是优化器根本不知道数据已经变了。
实操建议:
- 把统计信息更新写进同一事务批次:SQL Server 示例:
UPDATE dbo.Orders SET status = 'shipped' WHERE created_date UPDATE STATISTICS dbo.Orders (status);
- PostgreSQL 批处理中,
ANALYZE必须在COMMIT后执行(否则看不到新数据) - 监控项加一条:检查最近 1 小时内是否有大更新但无对应
ANALYZE记录(查pg_stat_all_tables的last_analyze)
统计信息不是“设一次就永远有效”的配置,它是优化器做决策的实时依据。批量更新后不碰它,等于告诉数据库:“你按三个月前的数据来猜现在的情况”。











