RMAN 恢复后同一条 SQL 执行计划变了?可能是 XMLIndex PATH TABLE统计信息丢了
RMAN 恢复后同一条 SQL 执行计划变了?可能是 XMLIndex PATH TABLE统计信息丢了
一、问题背景
最近在做一次数据库迁移验证时,遇到这样一个问题:
同一条查询 XML 数据的 SQL,在生产库跑得好好的,但在用 RMAN 恢复到测试环境后,执行计划完全变了,性能也差了很多。
生产库执行计划走索引,Cost 大约 189;测试库(RMAN 恢复)却走了全表扫描,Cost 只有 8。Cost 越低反而越慢,这明显不对劲。
二、问题 SQL
原始 SQL 大概长这样(已脱敏):
SELECT t.id_column, t.xml_column.getClobVal()
FROM customer t
WHERE xmlexists(...) AND xmlexists(...);
customer 表有一个 xml_column 字段,类型是 XMLType,上面建了 XMLIndex。
三、排查过程
1. 先看执行计划差异
生产库执行计划(走索引):
| Id | Operation | Name |
| 0 | SELECT STATEMENT | |
| 2 | NESTED LOOPS | |
| 3 | VIEW | VW_SQ_1 |
| 6 | PARTITION SYSTEM ALL | |
| 7 | TABLE ACCESS BY LOCAL INDEX ROWID BATCHED | SYS_IX_CUSTOMER_PATH_TABLE |
| 8 | INDEX RANGE SCAN | SYS_IX_CUSTOMER_VALUE_IX |
| 12 | TABLE ACCESS BY USER ROWID | customer |
测试库执行计划(走全表扫描):
| Id | Operation | Name |
| 0 | SELECT STATEMENT | |
| 1 | NESTED LOOPS SEMI | |
| 6 | PARTITION SYSTEM ALL | |
| 7 | TABLE ACCESS FULL | SYS_IX_CUSTOMER_PATH_TABLE |
| 12 | TABLE ACCESS BY USER ROWID | customer |
核心差异:对 SYS_IX_CUSTOMER_PATH_TABLE 这张表,生产库走 INDEX RANGE SCAN,测试库走 TABLE ACCESS FULL。
2. 再看统计信息差异
在 10053 trace 里找到这张表的统计信息:
生产库:
Table Stats::
Table: SYS_IX_CUSTOMER_PATH_TABLE
#Rows: 1910000000
#Blks: 11798016
测试库(RMAN 恢复后):
Table Stats::
Table: SYS_IX_CUSTOMER_PATH_TABLE
#Rows: 0
#Blks: 1
问题找到了:测试库中这张表的统计信息变成了 0 行 1 块。
因为优化器认为这张表几乎是空的,全表扫描成本只有 2,而走索引成本是 50,当然选全表扫描。
3. 更奇怪的一点
测试库中,这张表的索引统计信息却和生产库一样:
Index Stats::
Index: SYS_IX_CUSTOMER_PIKEY_IX
#LB: 7701480 #DK: 1810968580
Index: SYS_IX_CUSTOMER_VALUE_IX
#LB: 3649220 #DK: 3365124
这说明 RMAN 恢复其实把统计信息带过来了,但路径表的表级统计信息在恢复后被覆盖成了 0 行。
四、什么是 XMLIndex 路径表
SYS_IX_CUSTOMER_PATH_TABLE 这种表是 Oracle 的 XMLIndex 路径表(Path Table)。
1. 什么是 XMLIndex
XMLType 是 Oracle 用来存储 XML 数据的数据类型。当 XML 数据很大、结构复杂时,直接在 XML 列上做 XPath 查询会很慢。
XMLIndex 就是 Oracle 为 XMLType 设计的专用索引,类似于 Oracle Text 的 CONTEXT 索引,属于 domain index(域索引)。
当你在 XMLType 列上创建 XMLIndex 时,Oracle 会自动在后台创建一些内部对象:
- 路径表(Path Table):把 XML 文档的节点拆解成关系型行存储
- 二级索引:建立在路径表上的 B-tree 索引
2. 路径表里存什么
路径表通常包含这些列:
| 列名 | 含义 |
|---|---|
RID | 指向基表(customer)某行 XML 记录的 ROWID |
PATHID | XML 路径的编码标识 |
ORDER_KEY | 节点在 XML 文档中的顺序位置 |
LOCATOR | 节点定位信息 |
VALUE | 节点值 |
命名通常遵循:
SYS<index_id>_IX_<base_table_name>_PATH_TABLE
3. 路径表是透明的
Oracle 官方文档说:
“You need never explicitly gather statistics on the path table. You need only collect statistics on the XMLIndex index or the base table on which the XMLIndex index is defined; statistics are collected and maintained on the path table and its secondary indexes transparently.”
意思是:你不需要手动维护路径表统计信息,只要收集基表或 XMLIndex 索引的统计信息,Oracle 会自动维护路径表。
但现实是,这种自动维护机制在 RMAN 恢复等场景下可能出问题。
五、为什么 RMAN 恢复后统计信息没有同步
1. 一个常见误解
很多人以为 RMAN 恢复不会恢复统计信息。但从上面的 trace 可以看到,索引统计信息确实被恢复了,和生产库完全一致。
所以问题不是"RMAN 没同步统计信息",而是路径表的表级统计信息在恢复后被错误地覆盖了。
2. 可能的原因
XMLIndex 不是普通 B-tree 索引,而是 domain index。RMAN 恢复后,domain index 的内部对象(路径表)的统计信息维护逻辑和普通表不同,可能存在已知限制。查了官方文档没有找到相应的内容,只能做个猜测。
3. 为什么索引统计信息还是对的?
因为二级索引是普通的 B-tree 索引,RMAN 恢复时它们的统计信息被正常恢复了。但路径表作为 domain index 的内部对象,表级统计信息在恢复后被单独处理,导致了两边不一致。
六、解决方案
方案一:重新收集统计信息(根治,但可能慢)
-- 只收集路径表统计,索引统计已经正确就不用再收集了
EXEC DBMS_STATS.GATHER_TABLE_STATS(
ownname => 'APP_SCHEMA',
tabname => 'SYS_IX_CUSTOMER_PATH_TABLE',
cascade => FALSE, -- 跳过索引统计,节省时间
degree => 32, -- 并行
estimate_percent => 5
);
如果路径表很大(比如 19 亿行),收集统计信息可能很慢。可以先用 estimate_percent => 5 快速采样。
方案二:用 hint 临时绕过(应急)
SELECT /*+ CARDINALITY(t, 1900000000) */
t.id_column, t.xml_column.getClobVal()
FROM customer t
WHERE xmlexists(...) AND xmlexists(...);
如果代码无法修改,这种方式则不可取。
七、总结
这次问题的根本原因是:RMAN 恢复后的测试环境中,XMLIndex 路径表的表级统计信息丢失,变成了 0 行 1 块,导致优化器严重低估全表扫描成本,选择了错误的执行计划。
关键点:
- XMLIndex 路径表是 Oracle 自动生成的内部表,用来存储 XML 节点信息。
- RMAN 恢复会恢复索引统计信息,但路径表的表级统计信息不能恢复。
- 最稳妥的解决方案是重新收集统计信息。
- 临时应急可以用 hint 。
建议在 RMAN 恢复操作后,总是检查一下 XMLIndex 相关对象的统计信息是否正常,避免执行计划走样。
更多推荐


所有评论(0)