统计信息过期会导致查询变慢和计数失真,因其使优化器误判数据分布,错误选择执行计划;可通过STATS_DATE检查更新时间,超7天未更新即需警惕,并优先更新JOIN字段上的统计信息。

关联查询变慢,大概率不是数据量本身的问题,而是统计信息过期导致优化器选错连接方式或索引。
如何快速确认统计信息是否过期
别猜,直接查更新时间。SQL Server 中每个统计对象都有最后更新时间戳,超过 7 天未更新就该警惕:
SELECT
OBJECT_NAME(s.object_id) AS table_name,
s.name AS stats_name,
STATS_DATE(s.object_id, s.stats_id) AS last_updated,
DATEDIFF(day, STATS_DATE(s.object_id, s.stats_id), GETDATE()) AS days_since_update
FROM sys.stats s
WHERE OBJECT_NAME(s.object_id) IN ('orders', 'users', 'logs') -- 替换为你 JOIN 涉及的表
AND STATS_DATE(s.object_id, s.stats_id) < DATEADD(day, -7, GETDATE());如果结果里出现 days_since_update > 30,尤其在刚批量导入或高频写入后,基本可以锁定问题。
- 注意:
STATS_DATE返回NULL表示从未更新过,这种情况多见于新表或禁用自动统计的库 - 不要只看主键索引对应的统计信息,
JOIN字段(如user_id、order_no)上的统计信息更关键 - 分区表要单独检查每个分区的统计信息,
sys.dm_db_stats_properties可查更细粒度的修改计数
UPDATE STATISTICS 的参数选择很关键
默认不带参数的 UPDATE STATISTICS 是采样更新,对大表可能不准;但全扫描又太重。得按场景选:
- 紧急修复:用
WITH FULLSCAN,确保直方图和密度向量完全反映真实分布,适合JOIN字段值分布倾斜(比如 95% 的日志都属于 5 个用户) - 日常维护:用
WITH SAMPLE 30 PERCENT平衡速度与精度,但避免低于 10%,小样本在高基数列上容易失真 - 千万别用
sp_updatestats在生产高峰期跑——它会遍历所有表,包括你根本不用的归档表,IO 峰值可能拖垮整个实例
例如修复 orders 表的 user_id 统计信息:
UPDATE STATISTICS orders (IX_orders_user_id) WITH FULLSCAN;
为什么统计信息过期会让 JOIN 特别伤
优化器决定 JOIN 策略(嵌套循环 / 哈希 / 合并)时,极度依赖两件事:左右表的行数预估、JOIN 字段的选择性。统计信息一过期,这两项全错:
- 若
users表的status列统计显示“活跃用户仅占 2%”,实际已变成 60%,优化器仍按小结果集走嵌套循环,却把 600 万行当驱动表,性能断崖下跌 - 若
logs.user_id的直方图没更新,优化器误判某个user_id只有几十条日志,就会为每次 JOIN 准备键查找(Key Lookup),而不是一次性哈希连接 - 字符型
order_no和数字型id关联时,过期统计会让优化器低估隐式转换代价,宁可全表扫描也不愿走索引
最麻烦的是:这种问题不会报错,只会让执行计划悄悄变差。等你发现时,往往已经连带引发阻塞、超时、CPU 拉满——而根源只是某张表上一个没人管的统计信息快照。


















