num_rows非实时且常不准,因仅在analyze或dbms_stats后更新;分区表更易失真,需查last_analyzed并聚合非空分区num_rows,超7天未更新即不可信。
直接查 num_rows 是最快的方式,但它的值不是实时的,而是上一次统计收集时的快照 —— 用前必须确认它是否“够新”。
为什么 NUM_ROWS 常不准?
Oracle 不会自动更新 NUM_ROWS,它只在执行 ANALYZE TABLE 或 DBMS_STATS.GATHER_TABLE_STATS 后才写入。分区表更复杂:即使主表统计过,个别分区可能长期没刷新,导致 NUM_ROWS 为 NULL 或明显偏低。
- 查
USER_TAB_PARTITIONS.NUM_ROWS时,常见大量NULL值 —— 这不是漏数据,是该分区从未被单独统计过 -
USER_TAB_PARTITIONS.LAST_ANALYZED字段比NUM_ROWS更值得先看,它告诉你“这行数到底有多老” - RANGE 分区中,如果某分区刚插入大量数据但没触发自动统计(如未启用
MONITORING或未达采样阈值),NUM_ROWS就完全失真
查分区表总行数的正确 SQL 写法
别只查一张表的 NUM_ROWS,要聚合所有非空分区的值;同时过滤掉明显过期的记录(比如 LAST_ANALYZED 超过 7 天):
SELECT SUM(num_rows) AS total_estimated_rows FROM user_tab_partitions WHERE table_name = 'SALES' AND num_rows IS NOT NULL AND last_analyzed >= SYSDATE - 7;
- 用
SUM()而不是COUNT(*),因为你要的是行数总和,不是分区个数 - 必须加
num_rows IS NOT NULL,否则SUM返回NULL -
last_analyzed >= SYSDATE - 7是实用底线 —— 超过一周没更新的统计,在业务高峰期后基本不可信
什么时候必须放弃 NUM_ROWS,改用 COUNT(*)?
以下任一情况出现,就说明 NUM_ROWS 已失效,硬查只会误导判断:
-
SELECT COUNT(*) FROM user_tab_partitions WHERE table_name = 'X' AND num_rows IS NULL返回 > 0 —— 至少一个分区没统计过 -
MAX(last_analyzed)和MIN(last_analyzed)相差超过 30 天 —— 分区统计严重不同步 - 业务刚完成大批量
INSERT/EXCHANGE PARTITION操作,且未主动调用DBMS_STATS - 需要精确值用于归档、迁移或审计,而非容量预估
此时应并行扫分区:SELECT /*+ parallel(t, 4) */ COUNT(*) FROM sales PARTITION (P_2024_Q1) t;,再手工加总 —— 别试图用单条 SQL 扫全表,容易锁表或超时。
刷新统计信息的最小代价操作
想让 NUM_ROWS 可靠,又不想全表重刷?优先做两件事:
- 对新增/变更大的分区单独刷新:
EXEC DBMS_STATS.GATHER_TABLE_STATS('SCOTT', 'SALES', PARTNAME => 'P_2024_Q2'); - 用
ESTIMATE_PERCENT => DBMS_STATS.AUTO_SAMPLE_SIZE,避免手动设采样率出错 - 禁用
NO_INVALIDATE => FALSE(默认为TRUE),防止刷新后执行计划缓存失效引发性能抖动
真正容易被忽略的点:分区表的 NUM_ROWS 是“各分区之和”,但 Oracle 并不保证这个和等于 USER_TABLES.NUM_ROWS —— 后者可能还是旧的全局统计值,二者不一致是常态,别花时间对齐它们。











