首批通过分布式安全可靠测评,为关键业务系统打造
排查 OceanBase 数据库 V4.x 环境中 MySQL 模式下,无主键表生成列做分区键时,表中存在 local unique index 时,存在潜在 core 问题
更新时间:2026-05-21 01:56
问题现象
OceanBase 数据库 V4.x 环境中 MySQL 模式下,无主键表生成列做分区键时,表中存在 local unique index 时,存在潜在 core 问题。
复现 core 问题,示例。
创建无主键,以生成列作为分区键的 hash 分区表 t5。
obclient [mysql]> create table t5(c1 int, c2 int, c3 int as (c1 + c2) virtual) partition by hash(c3) partitions 4; Query OK, 0 rows affected (0.322 sec)创建唯一索引 t5_idx_01,并且显式包含了分区键生成列 c3。
obclient [mysql]> create unique index t5_idx_01 on t5(c1,c3) local; Query OK, 0 rows affected (0.627 sec)创建唯一索引 t5_idx_02,由于唯一索引必须包含分区键,c1 和 c2是 分区键生成列 c3 列的依赖列,唯一索引创建成功。
obclient [mysql]> create unique index t5_idx_02 on t5(c1,c2) local; Query OK, 0 rows affected (0.438 sec)执行 replace 操作,成功。
obclient [mysql]> replace into t5(c1, c2) values(1,1); Query OK, 1 row affected (0.054 sec)执行 replace 操作,失败,core。
obclient [mysql]> replace into t5(c1, c2) values(1,1); ERROR 2013 (HY000): Lost connection to MySQL server during query
关键诊断信息
触发条件
OceanBase 数据库 V4.x 环境,MySQL 模式,无主键表生成列做分区键,表中存在 local unique index。
事前巡检
对已有的 OceanBase 数据库 V4.x 环境中的 MySQL 租户中的表进行扫描,得到存在潜在风险的表,并且修改此表中可能会出现问题的索引。
事后诊断
已有环境潜在问题的排查步骤。
步骤一。
OceanBase 数据库 V4.x 环境下,在 SYS 租户中,确认当前的 MySQL 兼容模式下的用户租户中,是否存在分区键为生成列的无主键表,且在此表上存在没有包含分区键的
local unique index。在系统租户下,查询语句如下。obclient > WITH tmp_vc1 AS ( select vc1.tenant_id as tenant_id, vc1.table_id as table_id, vc1.column_name as column_name from oceanbase.__all_virtual_column vc1 INNER JOIN (select tenant_id, table_id from ( select vc.tenant_id as tenant_id, vc.table_id as table_id, vc.column_name as column_name from oceanbase.__all_virtual_column vc INNER JOIN (select table_id from oceanbase.__all_virtual_table where table_type = 3 and part_level in (1,2) and table_name not like '%__recycle_$%' and tenant_id in (select tenant_id from oceanbase.__all_tenant where compatibility_mode = 0 and tenant_name != 'sys' and tenant_name not like '%META$%')) AS sub1 on vc.table_id = sub1.table_id) group by table_id HAVING SUM(CASE WHEN column_name = '__pk_increment' THEN 1 ELSE 0 END) > 0) ERR_T on vc1.tenant_id = ERR_T.tenant_id and vc1.table_id = ERR_T.table_id and (vc1.column_flags & 1 = 1 or vc1.column_flags & 2 = 1)), final_data_table as ( select VT_P_I.tenant_id, VT_P_I.table_id, VT_P_I.part_expr from oceanbase.__all_virtual_part_info VT_P_I INNER JOIN tmp_vc1 on VT_P_I.tenant_id = tmp_vc1.tenant_id and VT_P_I.table_id = tmp_vc1.table_id and find_in_set(VT_P_I.part_expr, tmp_vc1.column_name)), index_table as ( select VT.tenant_id, VT.table_id from oceanbase.__all_virtual_table VT inner join final_data_table fdt on VT.tenant_id = fdt.tenant_id and VT.data_table_id = fdt.table_id and VT.index_type=2), new_column_set as (select distinct NEW_VCT.tenant_id as tenant_id, NEW_VCT.table_id as table_id from ( select new_vc.tenant_id, new_vc.table_id, new_vc.column_name from oceanbase.__all_virtual_column new_vc inner join index_table idt on new_vc.tenant_id = idt.tenant_id and new_vc.table_id = idt.table_id) NEW_VCT inner join tmp_vc1 on NEW_VCT.tenant_id = tmp_vc1.tenant_id group by table_id HAVING sum(case when NEW_VCT.column_name = tmp_vc1.column_name then 1 else 0 end) = 0), target_index_table as (select distinct final_all_t.tenant_id, final_all_t.table_id, final_all_t.table_name, final_all_t.data_table_id from oceanbase.__all_virtual_table final_all_t inner join new_column_set on final_all_t.tenant_id = new_column_set.tenant_id and final_all_t.table_id = new_column_set.table_id order by table_id) select distinct ft.tenant_id, ft.table_id, ft.table_name from oceanbase.__all_virtual_table ft inner join target_index_table on ft.tenant_id = target_index_table.tenant_id and ft.table_id = target_index_table.data_table_id order by table_id;通过以上的查询语句就可以看到当前集群中的 MySQL 租户中,是否存在分区键为生成列的无主键表,且在此表上存在没有包含分区键的
local unique index。查询结果如下,说明表 t5、t6、t7 中存在符合条件的索引,需要进行调整。输出结果如下:
+-----------+----------+------------+ | tenant_id | table_id | table_name | +-----------+----------+------------+ | 1004 | 500005 | t5 | | 1004 | 500020 | t6 | | 1004 | 500035 | t7 | +-----------+----------+------------+ 3 rows in set (3.295 sec)步骤二。
如果需要详细查看具体是哪些索引符合以上条件,可以在系统租户下执行以下语句。
WITH tmp_vc1 AS ( select vc1.tenant_id as tenant_id, vc1.table_id as table_id, vc1.column_name as column_name from oceanbase.__all_virtual_column vc1 INNER JOIN (select tenant_id, table_id from ( select vc.tenant_id as tenant_id, vc.table_id as table_id, vc.column_name as column_name from oceanbase.__all_virtual_column vc INNER JOIN (select table_id from oceanbase.__all_virtual_table where table_type = 3 and part_level in (1,2) and table_name not like '%__recycle_$%' and tenant_id in (select tenant_id from oceanbase.__all_tenant where compatibility_mode = 0 and tenant_name != 'sys' and tenant_name not like '%META$%')) AS sub1 on vc.table_id = sub1.table_id) group by table_id HAVING SUM(CASE WHEN column_name = '__pk_increment' THEN 1 ELSE 0 END) > 0) ERR_T on vc1.tenant_id = ERR_T.tenant_id and vc1.table_id = ERR_T.table_id and (vc1.column_flags & 1 = 1 or vc1.column_flags & 2 = 1)), final_data_table as ( select VT_P_I.tenant_id, VT_P_I.table_id, VT_P_I.part_expr from oceanbase.__all_virtual_part_info VT_P_I INNER JOIN tmp_vc1 on VT_P_I.tenant_id = tmp_vc1.tenant_id and VT_P_I.table_id = tmp_vc1.table_id and find_in_set(VT_P_I.part_expr, tmp_vc1.column_name)), index_table as ( select VT.tenant_id, VT.table_id from oceanbase.__all_virtual_table VT inner join final_data_table fdt on VT.tenant_id = fdt.tenant_id and VT.data_table_id = fdt.table_id and VT.index_type=2), new_column_set as (select distinct NEW_VCT.tenant_id as tenant_id, NEW_VCT.table_id as table_id from ( select new_vc.tenant_id, new_vc.table_id, new_vc.column_name from oceanbase.__all_virtual_column new_vc inner join index_table idt on new_vc.tenant_id = idt.tenant_id and new_vc.table_id = idt.table_id) NEW_VCT inner join tmp_vc1 on NEW_VCT.tenant_id = tmp_vc1.tenant_id group by table_id HAVING sum(case when NEW_VCT.column_name = tmp_vc1.column_name then 1 else 0 end) = 0) select distinct final_all_t.tenant_id, final_all_t.table_id, final_all_t.table_name, final_all_t.data_table_id from oceanbase.__all_virtual_table final_all_t inner join new_column_set on final_all_t.tenant_id = new_column_set.tenant_id and final_all_t.table_id = new_column_set.table_id order by table_id;结果如下所示,可以看到符合条件的索引以及其对应的主表 id。
输出结果如下:
+-----------+----------+------------------------+---------------+ | tenant_id | table_id | table_name | data_table_id | +-----------+----------+------------------------+---------------+ | 1004 | 500019 | __idx_500005_t5_idx_02 | 500005 | | 1004 | 500034 | __idx_500020_t6_idx_02 | 500020 | | 1004 | 500049 | __idx_500035_t6_idx_02 | 500035 | +-----------+----------+------------------------+---------------+ 3 rows in set (3.133 sec)
问题原因
core 原因是 replace into 在 try_insert 时会将当前索引中冗余的主表主键返回回去,用于使得 replace_into 算子获知发生 unique 冲突的冲突行就需要使用主表的 隐藏自增列 + 分区键 来唯一的描述一行,但是上述的两个索引 t5_idx_01 和 t5_idx_02 分别冗余的主表 c3 列和 c1 + c2 列,t5_idx_02 索引中是没有冗余主表分区键 c3 列的,那么在写 t5_idx_02 时假设发生 unique 冲突,想要准确的描述出 当前发生 unique 冲突行对应主表的唯一键,就需要返回主表的隐藏自增列 + c1 + c2,但是 t5_idx_01 时假设发生 unique 冲突,只需要返回主表的隐藏自增列 + c3,那么从 DML 的层面上看,主表写一行数据,返回的多个冲突行的列数是完全不同的,t5_idx_02 返回 3 列,t5_idx_01 返回 2 列,数据无法统一处理,引起了 core。
问题的风险及影响
OceanBase 数据库 V4.x 环境下,如果主表存在文中所描述的局部唯一索引,在 replace into 操作时,可能会导致 OBServer core 掉。
影响租户
影响 OceanBase 数据库中的 MySQL 租户,对于 SYS 租户和 Oracle 租户无影响。
影响版本
OceanBase 数据库企业版 V4.1.0 GA(oceanbase-4.1.0.0-100001122023040322)及之后版本、V4.2.0 GA(oceanbase-4.2.0.0-100010082023083014)及之后版本、V4.2.1 GA(oceanbase-4.2.1.0-100000182023092722)及之后版本、V4.2.2 GA(oceanbase-4.2.2.0-100000082024011317)及之后版本、V4.3.0(oceanbase-4.3.0.0-100000072024020200)及之后版本。
解决方法及规避方式
解决方法:
解决方法一。
升级至问题已修复版本。目前已修复的版本包括 OceanBase 数据库企业版 V4.2.1 BP8(oceanbase-4.2.1.8-108000052024072217)及之后版本、V4.2.4(oceanbase-4.2.4.0-100000252024070621)及之后版本。
解决方法二。
如果用户遇到此类问题,需要重新拉起 OBServer,并且使用本文档中事后诊断所描述的排查步骤去删除掉可能出现异常的局部唯一索引,并且重新构建包含分区键的唯一索引。
在租户 MySQL 模式下针对存在以上条件的索引执行
alter table drop操作删除掉相关索引,之后重新构建新的索引。注意
新的索引必须要包含分区键,例如分区键是 c3 列,那么索引中必须显式指定 c3 列。
规避方式:
利用本文档中描述的步骤去重新构建存在潜在风险的局部唯一索引。