DBMS_STATS.GATHER_TABLE_STATS 默认仅持ROW SHARE锁,加degree=>1、cascade=>FALSE、no_invalidate=>FALSE等参数可避免锁升级和计划延迟生效。
DBMS_STATS.GATHER_TABLE_STATS 怎么用才不锁表
默认情况下 gather_table_stats 会请求 row share(即 ss)锁,对 dml 影响小,但若指定 degree > 1 或启用并行、或分析大索引时,可能升级为 share 锁,阻塞 ddl(比如 alter table)。关键不是“能不能用”,而是“怎么配参数避开锁升级”。
- 加
degree => 1强制单线程,避免并行引发的额外锁争用 - 跳过索引统计:设
cascade => FALSE,单独用GATHER_INDEX_STATS控制时机 - 生产环境慎用
method_opt => 'FOR ALL COLUMNS SIZE AUTO',它会触发列数据采样扫描,加重 I/O 和锁持有时间;改用'FOR ALL COLUMNS SIZE 1'先保基础直方图 - 如果表有大量空块或 HWM 偏高,
estimate_percent => DBMS_STATS.AUTO_SAMPLE_SIZE可能误判采样率,建议显式设为10或20并观察执行计划变化
索引统计没更新?检查 cascade 和 no_invalidate 参数
调了 GATHER_TABLE_STATS 却发现执行计划没变,大概率是索引统计没生效——不是没收集,而是没“挂上”。Oracle 默认把索引统计和表统计解耦,cascade 控制是否顺带收索引,而 no_invalidate 决定已缓存的游标是否立即失效。
-
cascade => TRUE才会真正调用GATHER_INDEX_STATS;设成FALSE(默认值)时,索引统计完全不动 -
no_invalidate => TRUE(默认)会让旧执行计划继续用老统计信息,哪怕新统计已入库;临时查效果可设no_invalidate => FALSE,但注意这会硬解析所有相关 SQL,高峰期慎用 - 验证索引是否真更新:查
dba_indexes的last_analyzed,别只看dba_tables
DBMS_STATS.LOCK_TABLE_STATS 锁住之后怎么解锁
LOCK_TABLE_STATS 不是“锁表”,而是锁住该表的统计信息,让后续 GATHER_* 调用直接报错退出,防止被意外覆盖。但它不会释放,也不会随 session 结束自动解,必须显式解锁。
- 解锁命令就是
DBMS_STATS.UNLOCK_TABLE_STATS,传同样ownname和tabname - 如果忘了谁锁的,查
dba_tab_statistics的stattype_locked字段,值为'ALL'表示全锁,'DATA'表示只锁数据统计(不含列/直方图) - 锁住后强行 gather 会报错:
ORA-20000: Unable to lock statistics on ...,不是权限问题,别浪费时间查 grant
增量统计(INCREMENTAL)为什么有时反而更慢
对分区表开 incremental => TRUE 本意是只扫新增分区,但实际可能比全量还慢——因为要先查 dba_tab_modifications 判定哪些分区被改过,再触发全局统计合并(DBMS_STATS.MERGE_PART_STATS),中间还有哈希聚合和跨分区 join。
- 仅当满足两个条件才真正省事:① 分区键列有准确的
GLOBAL AND PARTITION统计,②publish => TRUE且未被锁住 - 如果表长期没跑
FLUSH_DATABASE_MONITORING_INFO,dba_tab_modifications数据不准,增量逻辑会退化为全扫 - 首次启用增量统计前,必须先跑一次全量 +
SET_TABLE_PREFS开'INCREMENTAL'='TRUE',否则ESTIMATE_PERCENT仍按全局算,不走增量路径
最常被忽略的是:DBMS_STATS 的统计结果不是实时生效的,它写入数据字典后,CBO 下次硬解析才读新值;如果应用用绑定变量+软解析多,可能等几小时都看不到执行计划变化——这不是统计没更新,是计划缓存太牢。









