问题现象
在执行扩/缩容过程中,在执行 transfer 操作的时候,有一个获取表锁的操作,而获取表锁需要拿到最新的 schema 信息(一定会刷一次), 走到刷 full schema 的路径,这个路径需要不断查询 __all_table_history 获取 schema 的版本,当用户业务模型需要频繁执行 truncate 操作时,table history 的数据量比较多,导致刷 schema 比较慢。
进一步排查复现,transfer 过程中调用的一条 inner sql 没有预期走上索引,而是走了全表扫描的计划。
SQL 如下:
SELECT
/*+index(__all_table_history idx_data_table_id)*/
table_id,
table_type,
index_type
FROM (
SELECT TABLE_ID,
SCHEMA_VERSION,
TABLE_TYPE,
INDEX_TYPE,
IS_DELETED,
ROW_NUMBER() OVER (
PARTITION BY TABLE_ID
ORDER BY SCHEMA_VERSION DESC
) AS RN
FROM __all_table_history
WHERE TENANT_ID = 0
AND DATA_TABLE_ID = 630518
) V
WHERE RN = 1
and IS_DELETED = 0
ORDER BY TABLE_ID;
当 __all_table_history 表数据量多的时候,预期此条SQL走索引,减少扫描的数据量。但是,观察发现,清空 plan cache 之后,首先会生成索引计划,后面 10 分钟左右就会走全表扫描计划,且之后 99% 以上的 SQL 都会走全表扫描计划。
查询 oceanbase.GV$OB_PLAN_CACHE_PLAN_STAT -> OUTLINE_DATA,可以看到这条 inner sql 大部分走了全表扫描的计划。
select PLAN_ID,PLAN_HASH,SQL_ID,LAST_ACTIVE_TIME, HIT_COUNT,OUTLINE_DATA,EXECUTIONS from oceanbase.GV$OB_PLAN_CACHE_PLAN_STAT where sql_id='0E5FD3A1861A82D56920379CA2AAFB66' order by LAST_ACTIVE_TIME desc ;
+---------+----------------------+----------------------------------+----------------------------+-----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
| PLAN_ID | PLAN_HASH | SQL_ID | LAST_ACTIVE_TIME | HIT_COUNT | OUTLINE_DATA | EXECUTIONS |
+---------+----------------------+----------------------------------+----------------------------+-----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------+
| 206559 | 6325863757586186942 | 0E5FD3A1861A82D56920379CA2AAFB66 | 2025-02-24 19:03:16.183130 | 303969 | /*+BEGIN_OUTLINE_DATA PQ_DISTRIBUTE_WINDOW(@"SEL$ECB627EE" (0) NONE) INDEX_DESC(@"SEL$ECB627EE" "oceanbase"."__all_table_history"@"SEL$2" "primary") PROJECT_PRUNE(@"SEL$2") PRED_DEDUCE(@"SEL$1") PRED_DEDUCE(@"SEL$2F8A4177") PROJECT_PRUNE(@"SEL$ED434F45") OPTIMIZER_FEATURES_ENABLE('4.3.5.0') END_OUTLINE_DATA*/ | 303970 |
| 226129 | 13233836425377909529 | 0E5FD3A1861A82D56920379CA2AAFB66 | 2025-02-24 19:00:23.769543 | 8877 | /*+BEGIN_OUTLINE_DATA PQ_DISTRIBUTE_WINDOW(@"SEL$ECB627EE" (0) NONE) INDEX(@"SEL$ECB627EE" "oceanbase"."__all_table_history"@"SEL$2" "idx_data_table_id") PROJECT_PRUNE(@"SEL$2") PRED_DEDUCE(@"SEL$1") PRED_DEDUCE(@"SEL$2F8A4177") PROJECT_PRUNE(@"SEL$ED434F45") OPTIMIZER_FEATURES_ENABLE('4.2.1.0') END_OUTLINE_DATA*/ | 8878 |
| 142010 | 6677751919859152985 | 0E5FD3A1861A82D56920379CA2AAFB66 | 2025-02-24 19:00:23.769415 | 3000 | /*+BEGIN_OUTLINE_DATA PQ_DISTRIBUTE_WINDOW(@"SEL$ECB627EE" (0) NONE) INDEX_DESC(@"SEL$ECB627EE" "oceanbase"."__all_table_history"@"SEL$2" "idx_data_table_id") PROJECT_PRUNE(@"SEL$2") PRED_DEDUCE(@"SEL$1") PRED_DEDUCE(@"SEL$2F8A4177") PROJECT_PRUNE(@"SEL$ED434F45") OPTIMIZER_FEATURES_ENABLE('4.3.5.0') END_OUTLINE_DATA*/ | 3001 |
| 52615 | 6677751919859152985 | 0E5FD3A1861A82D56920379CA2AAFB66 | 2025-02-24 18:53:25.504703 | 3215 | /*+BEGIN_OUTLINE_DATA PQ_DISTRIBUTE_WINDOW(@"SEL$ECB627EE" (0) NONE) INDEX_DESC(@"SEL$ECB627EE" "oceanbase"."__all_table_history"@"SEL$2" "idx_data_table_id") PROJECT_PRUNE(@"SEL$2") PRED_DEDUCE(@"SEL$1") PRED_DEDUCE(@"SEL$2F8A4177") PROJECT_PRUNE(@"SEL$ED434F45") OPTIMIZER_FEATURES_ENABLE('4.3.5.0') END_OUTLINE_DATA*/ | 3216 |
关键诊断信息
触发条件
分区数量多(即使空分区),业务执行大量 truncate 表操作,导致 __all_table_history 数据量多。
事前巡检
集群是否有大量的分区且经常执行分区变更的操作, 查询分区 schema 的操作慢,且此条 inner sql 执行慢。
事后诊断
查询 gv$ob_sql_audit 表,查看 inner sql 的执行时间。
问题原因
执行慢的这条 inner sql,Hint /*+index(__all_table_history idx_data_table_id)*/ 当前绑定在外层查询中,而实际查询涉及到表 __all_table_history 是在内层查询中。 因为 SQL 会有被优化器改写的可能,如果子查询做了提升,这个 Hint 是生效的。如果子查询没有提升,是不生效的。

对于整型常量,会按照范围做匹配,截图里的两条 SQL 因为整型精度不一样,会匹配到不同的计划。

问题的风险及影响
当表的 schema 变化的场景,需要刷新 schema 的情况下,调用 inner sql,执行慢。一个典型场景就是刷 schema 慢可能导致负载均衡慢。
影响租户
影响 OceanBase 数据库中的 SYS 租户和 Oracle 租户以及 MySQL 租户。
影响版本
OceanBase 数据库企业版 V4.2.1 GA(oceanbase-4.2.1.0-100000182023092722)及之后版本、V4.2.5 GA(oceanbase-4.2.5.0-100000082024102022)及之后版本、V4.3.5 GA(oceanbase-4.3.5.0-100000122024123020)及之后版本。
解决方法
升级至问题已修复版本。目前已修复的版本包括 OceanBase 数据库企业版 V4.2.1 BP11(oceanbase-4.2.1.11-111000052025032520)及之后版本、V4.2.5 BP6(oceanbase-4.2.5.6-106000052025082216)及之后版本、V4.3.5 BP4(oceanbase-4.3.5.4-104000052025090918)及之后版本。
规避方式
在 OceanBase 数据库 V4.2.3 版本,内核加了新的限制,root 用户默认不能在 OceanBase 下绑定 outline。所以当前应急不能通过绑定 outline 来确保每次走上正确的计划,只能通过写脚本的方式,每隔 10 分钟,不断刷新此条 SQL 的 plan cache 尽量走上索引计划。