首批通过分布式安全可靠测评,为关键业务系统打造
异步行转列升级兼容问题
更新时间:2026-06-16 09:21
问题背景
oceanbase 表的 column group(以下简称cg)信息存在系统表中,具体如下:
__all_virtual_column_group:存储当前 schema 的 column group 编号等信息__all_virtual_column_group_history:存储所有历史 schema 的 column group 编号等信息__all_virtual_column_group_mapping:存储当前 schema 的 column group 列信息__all_virtual_column_group_mapping_history:存储所有历史 schema 的 column group 列信息
目前行列存格式变换关系如下:

异步行转列允许如下状态转换实时变更 schema,并且在合并的时候转换存储格式(也就是允许表由纯行存异步变更为纯列存表或者行列混存表):
- default cg -> (add each) -> each cg/rowkey cg
- all cg -> (add each) -> each cg/all cg
异步行转列为 online ddl,不会创建新的表来重写旧表数据。在处理旧表 schema 时(也就是
__all_virtual_column_group_history和__all_virtual_column_group_mapping_history中的记录),V4.3.5.0 上线版本选择不删旧 schema,在读 schema 的时候依赖 cg 编号做多版本读逻辑,但是旧 schema 中 cg 编号和新 schema 中的编号可能不一样,因此存在 cg 类型和其中包含的数据列错位的问题。
事前巡检
异步行转列 schema 问题首次修复版本为 V4.3.5 hotfix4 和 V4.3.5 BP1,用户想使用异步行转列功能时,首先检查集群版本是否大于等于上述版本,确定为包含修复的版本再使用该功能。
建议用户在升级集群时候检查一下是否在问题版本执行过异步行转列操作,详细操作请参考 规避方法。
事后诊断
场景1:用户在升级到 V4.3.5 hotfix4 之前的版本,执行异步行转列操作 all cg -> (add each) -> each cg/all cg,此时系统表旧版本 all cg 没有删除,考虑到旧版本的 all cg 编号和新的可能不一致,此时有概率出现兼容性问题,可以按照下述手段确认。
查询问题表,报错 4016 internal error。
按照问题表 table_id 查询系统表
__all_virtual_column_group_history,有旧 schema version 的 __co_all 存在。
如果异步行转列之后有做过合并,问题表对应 tablet 应该合并卡住,可以确认一下是否卡住。
obclient> select * from __all_virtual_sys_task_status where tenant_id = 1002 and task_type = 'SSTABLE_MAJOR_MERGE' limit 10;查看关键日志。
查询问题表对应tablet的合并日志,看 storage schema 中 column group array 是否存在。
observer.log:128617:[2025-01-20 17:11:42.972203] INFO [STORAGE.COMPACTION] get_medium_compaction_info (ob_basic_tablet_merge_ctx.cpp:1251) [3488022][T1006_CO_MERGE_][T1006][YB42AC16056E-00062C1D1BBA503B-0-0] [lt=25] get storage schema to merge(param={ObTabletMergeDagParam:{skip_get_tablet:true, merge_type:"MEDIUM_MERGE", merge_version:1736964000910610000, ls_id:{id:7007}, tablet_id:{id:2230623}, exec_mode:"EXEC_MODE_LOCAL", need_swap_tablet_flag:false, is_reserve_mode:false, schedule_transfer_seq:6}, start_cg_idx:0, end_cg_idx:0, compat_mode:0, dag_net_id:YB42AC16056E-00062C1D0E49C38B-0-0}, medium_info={compaction_type:"MAJOR_COMPACTION", merge_reason:"TENANT_MAJOR", medium_snapshot:1736964000910610000, last_medium_snapshot:1736907946352621000, tenant_id:1006, cluster_id:1718694755, medium_compat_version:5, data_version:17180067072, is_schema_changed:0, storage_schema:{this:0xfffa9a7c00a0, storage_schema_version:4, version:0, is_use_bloomfilter:0, column_info_simplified:0, compat_mode:0, table_type:3, index_type:0, row_store_type:1, schema_version:1736950505217584, is_cs_replica_compat:0, column_cnt:11, store_column_cnt:11, tablet_size:134217728, pctfree:0, block_size:16384, progressive_merge_round:1, master_key_id:18446744073709551615, compressor_type:6, encryption:"", encrypt_key:"", rowkey_cnt:1, rowkey_array:[cnt:1, {column_idx:16, meta_type:{type:"BIGINT UNSIGNED", collation:"binary", coercibility:"NUMERIC"}, order:0}], column_cnt:11, column_array:[cnt:11, {meta_type:{type:"BIGINT UNSIGNED", collation:"binary", coercibility:"NUMERIC"}, is_column_stored_in_sstable:1, is_rowkey_column:1, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"VARCHAR", collation:"utf8mb4_bin", coercibility:"INVALID"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"VARCHAR", collation:"utf8mb4_bin", coercibility:"INVALID"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"DATETIME", collation:"binary", coercibility:"NUMERIC"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"DATETIME", collation:"binary", coercibility:"NUMERIC"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"VARCHAR", collation:"utf8mb4_bin", coercibility:"INVALID"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"VARCHAR", collation:"utf8mb4_bin", coercibility:"INVALID"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"VARCHAR", collation:"utf8mb4_bin", coercibility:"INVALID"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"VARCHAR", collation:"utf8mb4_bin", coercibility:"INVALID"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"DATETIME", collation:"binary", coercibility:"NUMERIC"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}, {meta_type:{type:"DATETIME", collation:"binary", coercibility:"NUMERIC"}, is_column_stored_in_sstable:1, is_rowkey_column:0, is_generated_column:0, orig_default_value:{"NULL":"NULL"}}], skip_index_cnt:0, skip_idx_attr_array:[cnt:0], column_group_cnt:12, column_group_array:[cnt:12, {version:1, type:1, compressor_type:6, row_store_type:1, block_size:16384, schema_column_cnt:11, rowkey_column_cnt:3, schema_rowkey_column_cnt:1, column_cnt:13, column_idxs:null}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[0]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[3]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[4]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[5]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[6]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[7]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[8]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[9]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[10]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:11, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:11, column_idxs:[0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}, {version:1, type:3, compressor_type:6, row_store_type:3, block_size:16384, schema_column_cnt:1, rowkey_column_cnt:0, schema_rowkey_column_cnt:0, column_cnt:1, column_idxs:[12]}], has_all_column_group:true}, co_major_merge_type:"USE_RS_BUILD_SCHEMA_MATCH_MERGE", is_skip_tenant_major:0, contain_parallel_range:0, parallel_merge_info:{list_size:0, compat:1}, encoding_granularity:65536})
场景2:如果用户在升级到 V4.3.5 hotfix4 之前的版本,执行异步行转列操作 all cg -> (add each) -> each cg/all cg,也可能直接报错 4200,这种场景新加的 cg 和旧的 cg 存在 id 重复,直接被防御住,不需要做修复。

场景3:用户升级到 V4.3.5 hotfix4 之前的版本,执行异步行转列操作 default cg -> (add each) -> each cg/all cg,此时系统表中旧版本 default cg 没有删除。之后用户又升级到 V4.3.5 hotfix4 之后的版本,这时候代码中处理旧 default cg 的逻辑删除,会在合并的时候多出旧 default cg 的数据,报错 4002。

问题原因
针对场景1 进行分析如下:
异步行转列为 Online DDL,不会删除旧表,系统表中记录也不会删除,需要做多版本读取的逻辑。旧版本创建的表可能经过多轮 DDL 操作修改 cg 信息,all cg 的 cg id 被修改(新版本的固定为1001),不满足现有的多版本读逻辑的要求,会错误地把旧版本的 cg 读取出来。
复现流程如下:
在 V4.3.5 之前的表做如下 DDL。
obclient> create table single_cg_with_multi_columns (c1 int primary key, c2 int, c3 int) with column group (all columns, each column); obclient> create table all_cg_not_overwrite_0 (c1 int primary key) with column group (all columns); obclient> create table all_cg_not_overwrite_1 (c1 int primary key, c2 int, c3 int) with column group (all columns, each column); obclient> create table co_default_cg_error (c1 int primary key, c2 int, c3 int); obclient> insert into single_cg_with_multi_columns values (1, 2, 3), (2, 3, 4), (3, 4, 5); obclient> insert into all_cg_not_overwrite_0 values (1), (2), (3); obclient> insert into all_cg_not_overwrite_1 values (1, 2, 3), (2, 3, 4), (3, 4, 5); obclient> insert into co_default_cg_error values (1, 2, 3), (2, 3, 4), (3, 4, 5); obclient> alter table single_cg_with_multi_columns drop column group (all columns, each column); obclient> alter table single_cg_with_multi_columns add column group (all columns); obclient> alter table all_cg_not_overwrite_0 drop column group (all columns); obclient> alter table all_cg_not_overwrite_0 add column group (all columns); obclient> alter table all_cg_not_overwrite_1 drop column group (each column); obclient> alter table all_cg_not_overwrite_1 add column group (each column); obclient> alter table all_cg_not_overwrite_1 drop column group (all columns); obclient> alter table all_cg_not_overwrite_1 add column group (all columns); obclient> alter table all_cg_not_overwrite_1 drop column group (each column);升级到 V4.3.5 版本。
针对原先那些表做如下 DDL,此时表
single_cg_with_multi_columns会出现场景1 的情况,表all_cg_not_overwrite_0和all_cg_not_overwrite_1会出现场景2 的情况。obclient> alter table single_cg_with_multi_columns add column group (each column) delayed; obclient> alter table all_cg_not_overwrite_0 add column group (each column) delayed; obclient> alter table all_cg_not_overwrite_1 add column group (each column) delayed; obclient> alter table co_default_cg_error add column group (each column) delayed;再升级到 V4.3.5.1 版本,此时表
co_default_cg_error会出现场景3 的情况
适用版本
OceanBase 数据库 V4.3.5 版本。
解决方法
场景1:这种场景通常合并已经根据错误的 table schema 生成 storage schema,这时候需要先修改系统表,让旧版本的 column group 不可见,然后重建数据表。
确定长时间合并未完成的 tablet。
obclient> select * from __all_virtual_sys_task_status where tenant_id = $tenant_id and task_type = 'SSTABLE_MAJOR_MERGE' limit 10;确认列表中每个 tablet 对应的表是否是问题表,如果是问题表,执行以下操作。
直连 sys 租户,修改租户为问题租户。
obclient> alter system change tenant tenant_id = $tenant_id;确认是问题租户。
obclient> select effective_tenant_id();确认是问题表
obclient> select table_name from __all_table where tenant_id = 0 and table_id = $table_id;确认只有两个版本的 schema_version,小的为旧版本。
obclient> select unique(schema_version) from __all_column_group_history where table_id = $table_id;确认查出 1 行数据, is_delete 是 0,表示旧版本的 all columns 对应记录,记录下对应的 column group id。
obclient> select * from __all_column_group_history where table_id = $table_id and schema_version = $schema_version and COLUMN_GROUP_NAME = '__co_all';根据查出的旧版本 all columns 对应的 schema_version 和 column_group_id 查询,确认查出的数据行数和表列数一致, is_delete 是 0。
obclient> select * from __all_column_group_mapping_history where table_id = $table_id and schema_version = $schema_version and COLUMN_GROUP_ID = $column_group_id;修改系统表 __all_column_group_history
obclient> update __all_column_group_history set IS_DELETED = 1 where table_id = $table_id and schema_version = $schema_version and COLUMN_GROUP_NAME = '__co_all';修改系统表 __all_column_group_mapping_history
obclient> update __all_column_group_mapping_history set IS_DELETED = 1 where table_id = $table_id and schema_version = $schema_version and COLUMN_GROUP_ID = $column_group_id;直连 rootservice 所在的 sys 租户,修改原表可以读取。
obclient> alter system flush kvcache cache='schema_cache';创建新表,把原表数据导入新表。
删除原表。
场景2:该场景读 table schema 就会报错,不需要做修复,升级至 V4.3.5 hotfix4 版本之后可以使用异步行转列,之前做 offline ddl 行列存转换就行。
场景3:该场景的恢复方式和场景1 类似,但此时旧版本 cg 为 default cg,对应的 cg name 是 __co_default,同样需要在系统表中找到这个数据,并且刷新 schema 和重建表。
规避方法
- OceanBase V4.3.5 hotfix4 之前版本不要使用异步行转列。
- 用户在执行以下升级路径之前先检查一下是否在有 bug 的版本执行过异步行转列操作,尤其是开源用户。
升级前版本包括 V4.3.5.0,V4.3.5.0 hotfix1,V4.3.5.0 hotfix2,V4.3.5.0 hotfix3。
升级后版本包括 V4.3.5.0 hotfix4 及之后的 hotfix 版本,V4.3.5.1 及以后的版本。
检查方法:
查
__all_virtual_ddl_operation中 table 执行记录是否包括异步行转列操作,但是考虑到 ddl_operation 记录可能被清掉,查出来没有不一定真的没做过。查
__all_virtual_column_group_history中 table 是否存在未被删除的旧 schema 记录,可以先查一下 __co_default 出现次数大于 1 的 table_id,如果没有的话则没做过异步行转列。obclient> SELECT table_id, COUNT(*) as cnt FROM __all_virtual_column_group_history WHERE tenant_id = xxx AND column_group_name = '__co_default' GROUP BY table_id HAVING COUNT(*) > 1 ORDER BY cnt DESC LIMIT 10;如果查出记录,需要一个 table 一个 table check 一下,首先看一下
__all_virtual_column_group_history中除__co_default之外是否有其他 column group,没有的话则没做过异步行转列,如果有的话,检查一下旧__co_default之后是否有一个 delete 行记录,没有的话说明旧的 schema 可能没有删掉,可以联系技术支持确认一下。obclient> select * from __all_virtual_column_group_history where tenant_id = xx and table_id = xx;