首批通过分布式安全可靠测评,为关键业务系统打造
OceanBase 数据库 V4.x 版本中 sql_audit 视图中 partition_cnt 统计方式的变化
更新时间:2026-05-25 09:11
在 POC 性能调优测试 和 日常生产环境监控时,经常需要关注单条 SQL 访问的分区数量(对应 OceanBase 数据库 V2.x/V3.x 中 gv$sql_audit 视图和 OceanBase 数据库 V4.x 中 gv$ob_sql_audit 视图中的 partition_cnt 字段)。OLTP 业务中对于访问 Partition 数量大于 1 的 SQL 尤其需要警惕:如果访问分区数不符合预期,往往意味着执行计划发生了走偏。
本文主要描述不同 OceanBase 数据库版本的内部审计视图(gv$sql_audit 或 gv$ob_sql_audit)统计 partiton_cnt 方式的差异。
OceanBase 数据库 V4.x 版本中引入了 Tablet 的概念,建立了用户表分区到 Tablet 的映射,实现底层数据存储与 schema 元数据的解耦:用户创建的一个分区对应一个 Tablet,表(table)、分区 (Partition) 作为用户创建的逻辑对象,而 Tablet 作为实际承载数据的物理存储单位。Tablet 和 Partition 的概念有所不同,Partition 表示 Table 的一个数据分片,在一个 Partition 中除了包含主表数据之外,还包含对应的局部索引。一个 Partition 中可能包含一个或者多个 Tablet,例如 Partition 的一个局部索引就会对应一个 Tablet,同一个 Partition 的所有 Tablet 会强制绑定在一起,保证它们存储在一台机器上。适应这种变化,OceanBase 数据库内核在统计一条 SQL 访问的 partition_cnt 的方式也略有不同:对于 gv$ob_sql_audit 中的 partition_cnt 字段,在 OceanBase 数据库 V4.x 版本中不同的 Tablet 也都统计到里面了,即 OceanBase 数据库 V4.x 版本中的主表和局部索引表,现在都各自算一个 Partition,因此访问单个分区时,如果走了二级局部索引回表,则 partition_cnt=2;而在 OceanBase 数据库 V2.x/V3.x 版本中执行同样的 SQL 时, partition_cnt 则不会统计局部索引(partition_cnt=1)。
示例如下。
创建表 t1。
MySQL [test]> create table t1 (id int primary key, value int, name varchar(100)); Query OK, 0 rows affected (0.091 sec)向 t1 表插入数据。
MySQL [test]> insert into t1 values (1,1,'a'),(2,2,'b'),(3,3,'c'),(4,4,'d'),(5,5,'e'); Query OK, 5 rows affected (0.011 sec) Records: 5 Duplicates: 0 Warnings: 0创建索引 idx1 。
MySQL [test]> create index idx1 on t1(value); Query OK, 0 rows affected (0.506 sec)执行如下查询语句。
MySQL [test]> select * from t1 where value=3;输出结果如下:
+----+-------+------+ | id | value | name | +----+-------+------+ | 3 | 3 | c | +----+-------+------+ 1 row in set (0.036 sec)展示执行计划。
MySQL [test]> explain select * from t1 where value=3; -- 先走二级局部索引,再回表输出结果如下:
+----------------------------------------------------------------------+ | Query Plan | +----------------------------------------------------------------------+ | ========================================================= | | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| | | --------------------------------------------------------- | | |0 |EXCHANGE IN REMOTE | |1 |10 | | | |1 |└─EXCHANGE OUT REMOTE| |1 |9 | | | |2 | └─TABLE RANGE SCAN |t1(idx1)|1 |7 | | | ========================================================= | | Outputs & filters: | | ------------------------------------- | | 0 - output([t1.id], [t1.value], [t1.name]), filter(nil) | | 1 - output([t1.id], [t1.value], [t1.name]), filter(nil) | | 2 - output([t1.id], [t1.value], [t1.name]), filter(nil), rowset=16 | | access([t1.id], [t1.value], [t1.name]), partitions(p0) | | is_index_back=true, is_global_index=false, | | range_key([t1.value], [t1.id]), range(3,MIN ; 3,MAX), | | range_cond([t1.value = 3]) | +----------------------------------------------------------------------+ 16 rows in set (0.004 sec)执行如下查询语句。
MySQL [test]> select * from t1 where id=3;输出结果如下:
+----+-------+------+ | id | value | name | +----+-------+------+ | 3 | 3 | c | +----+-------+------+ 1 row in set (0.004 sec)展示执行计划。
MySQL [test]> explain select * from t1 where id=3; -- 走主键索引,无需回表输出结果如下:
+----------------------------------------------------------------------+ | Query Plan | +----------------------------------------------------------------------+ | ===================================================== | | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| | | ----------------------------------------------------- | | |0 |EXCHANGE IN REMOTE | |1 |8 | | | |1 |└─EXCHANGE OUT REMOTE| |1 |7 | | | |2 | └─TABLE GET |t1 |1 |5 | | | ===================================================== | | Outputs & filters: | | ------------------------------------- | | 0 - output([t1.id], [t1.value], [t1.name]), filter(nil) | | 1 - output([t1.id], [t1.value], [t1.name]), filter(nil) | | 2 - output([t1.id], [t1.value], [t1.name]), filter(nil), rowset=16 | | access([t1.id], [t1.value], [t1.name]), partitions(p0) | | is_index_back=false, is_global_index=false, | | range_key([t1.id]), range[3 ; 3], | | range_cond([t1.id = 3]) | +----------------------------------------------------------------------+ 16 rows in set (0.003 sec)通过 gv$ob_sql_audit 视图中的字段 query_sql(实际的 SQL 语句)获取 partition_cnt(请求涉及的分区数)值。
## -- 在 OceanBase 数据库 V3.2.4 版本中 MySQL [oceanbase]> select query_sql,partition_cnt from gv$sql_audit where tenant_id=1001 and query_sql like 'select * from t1%';输出结果如下:
+--------------------------------+---------------+ | query_sql | partition_cnt | +--------------------------------+---------------+ | select * from t1 where value=3 | 1 | <== 走的是二级局部索引TABLE SCAN|t1(idx1) | select * from t1 where id=3 | 1 | <== 走的是主键索引TABLE GET|t1 +--------------------------------+---------------+ 2 rows in set (0.15 sec)向 t1 表插入数据。
MySQL [test]> insert into t1 values (1,1,'a'),(2,2,'b'),(3,3,'c'),(4,4,'d'),(5,5,'e'); Query OK, 5 rows affected (0.005 sec) Records: 5 Duplicates: 0 Warnings: 0MySQL [test]> insert into t1 values(6,6,'f'); Query OK, 1 row affected (0.003 sec)通过 gv$ob_sql_audit 视图中的字段 query_sql(实际的 SQL 语句)获取 partition_cnt(请求涉及的分区数)值。
MySQL [oceanbase]> select query_sql,partition_cnt from gv$ob_sql_audit where tenant_id=1002 and query_sql like 'insert into t1 values%';输出结果如下:
+-------------------------------------------------------------------------+---------------+ | query_sql | partition_cnt | +-------------------------------------------------------------------------+---------------+ | insert into t1 values (1,1,'a'),(2,2,'b'),(3,3,'c'),(4,4,'d'),(5,5,'e') | 2 | | insert into t1 values(6,6,'f') | 2 | +-------------------------------------------------------------------------+---------------+ 2 rows in set (1.328 sec)MySQL [oceanbase]> select query_sql,partition_cnt from gv$sql_audit where tenant_id=1001 and query_sql like 'insert into t1 values%';输出结果如下:
+-------------------------------------------------------------------------+---------------+ | query_sql | partition_cnt | +-------------------------------------------------------------------------+---------------+ | insert into t1 values (1,1,'a'),(2,2,'b'),(3,3,'c'),(4,4,'d'),(5,5,'e') | 1 | | insert into t1 values(6,6,'f') | 1 | +-------------------------------------------------------------------------+---------------+ 2 rows in set (0.14 sec)注意
一个表上如果有 10 个局部索引,针对单行的插入,V3.x 版本中会记录一个分区,而在 V4.x 版本中则会记录 11 个分区。
通过 gv$ob_sql_audit 视图中的字段 query_sql(实际的 SQL 语句)获取 partition_cnt(请求涉及的分区数)值。
## -- 在 OceanBase 数据库 V4.x 版本中 MySQL [oceanbase]> select query_sql,partition_cnt from gv$ob_sql_audit where tenant_id=1002 and query_sql like 'select * from t1%';输出结果如下:
+--------------------------------+---------------+ | query_sql | partition_cnt | +--------------------------------+---------------+ | select * from t1 where value=3 | 2 | <== 先走二级局部索引,再回表 | select * from t1 where id=3 | 1 | <== 走主键索引,无需回表 +--------------------------------+---------------+ 2 rows in set (1.338 sec)说明
对于上述同样的 SQL 查询,OceanBase 数据库 V4.x 版本中实际的数据库执行成本 COST 应该跟原来差不多,只是
partition_cnt统计方式上的变化。
适用版本
OceanBase 数据库 V2.x、V3.x、V4.x 版本。