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
PATHIDXML 路径的编码标识
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 块,导致优化器严重低估全表扫描成本,选择了错误的执行计划。

关键点:

  1. XMLIndex 路径表是 Oracle 自动生成的内部表,用来存储 XML 节点信息。
  2. RMAN 恢复会恢复索引统计信息,但路径表的表级统计信息不能恢复。
  3. 最稳妥的解决方案是重新收集统计信息。
  4. 临时应急可以用 hint 。

建议在 RMAN 恢复操作后,总是检查一下 XMLIndex 相关对象的统计信息是否正常,避免执行计划走样。

更多推荐