mysql模式下OB,不包含分区的表,UNIQUE KEY是否区分GLOBAL 和 LOCAL

【 使用环境 】生产环境
【 OB or 其他组件 】OceabBase_CE 4.2.1.2
【 使用版本 】OceabBase_CE 4.2.1.2
【问题描述】问题如题,mysql模式下OB的UNIQUE KEY是否区分GLOBAL 和 LOCAL。存在两种现象,包含分区的表和不包含分区的表都将UNIQUE KEY设置为了GLOBAL。创建完成后,只有包含分区的表的UNIQUE KEY被设置为了GLOBAL,不包含分区的从ODC的DDL信息看,是LOCAL属性的。请问是不是只有包含分区的表,UNIQUE KEY才区分LOCAL和GLOBAL。不包含分区的表,就算创建表的sql语句显示的将UNIQUE KEY指定为GLOABL,最终也是LOCAL的。
【复现路径】问题出现前后相关操作
【附件及日志】推荐使用OceanBase敏捷诊断工具obdiag收集诊断信息,详情参见链接(右键跳转查看):

【SOP系列 22 】——故障诊断第一步(自助诊断和诊断信息收集)

【备注】基于 LLM 和开源文档 RAG 的论坛小助手已开放测试,在发帖时输入 [@论坛小助手] 即可召唤小助手,欢迎试用!

1 个赞

根据您的描述,在 MySQL 模式下创建不包含分区的表时,您观察到即使显式指定了 GLOBAL 关键字,创建的唯一索引(UNIQUE KEY)似乎仍然表现为 LOCAL。为了澄清这个问题,我们可以参考 OceanBase 文档中的相关信息。

在 OceanBase 中,对于不包含分区的表(即非分区表),创建唯一索引时是否区分 LOCALGLOBAL 关键字的行为如下:

  1. 对于非分区表

    • 根据 OceanBase 的文档,对于非分区表,创建唯一索引时,无论是否指定 LOCALGLOBAL 关键字,创建的都是 局部索引 (LOCAL)。这意味着即使您在创建唯一索引时指定了 GLOBAL 关键字,实际创建的索引仍然是局部索引。
  2. 对于分区表

    • 对于分区表,创建唯一索引时,如果指定了 LOCAL 关键字,则创建的是局部索引;如果指定了 GLOBAL 关键字,则创建的是全局索引。如果未指定 LOCALGLOBAL 关键字,默认创建的是全局索引。

因此,对于您的情况,当您在 MySQL 模式下创建一个不包含分区的表,并且尝试创建一个带有 GLOBAL 关键字的唯一索引时,实际创建的将是局部索引。这是因为对于非分区表,OceanBase 不区分 LOCALGLOBAL 关键字。

综上所述,您的观察结果与 OceanBase 的行为是一致的。对于非分区表,无论是否显式指定 GLOBAL 关键字,创建的唯一索引都是局部索引。

如果您希望进一步验证这一点,可以通过查询系统视图来检查索引的实际类型。例如,您可以使用如下 SQL 查询来检查索引的类型:

SELECT index_type FROM __all_index WHERE table_name = 'your_table_name' AND index_name = 'your_index_name';

其中 index_type 字段可以帮助您确定索引是局部索引还是全局索引。如果 index_type 显示为 LOCAL,则表明创建的是局部索引。

希望这些信息对您有所帮助!如果有其他疑问或需要进一步的帮助,请随时告知。

具体信息可参考以下文档:

2 个赞

这个机器人回答的还是不错的,很有条理。
不过关于 __all_index 的那一段看起来犯了跟 chatgpt 一样的错误:一本正经的胡说八道。

翻遍 OB 的官网,没有看到有视图叫 __all_index ,在 OB 里也没有视图字段直接说明一个索引到底是全局索引还是本地索引。需要借助理论自己去分析判断。

理论部分

一个普通的表,其数据和索引,OB 是放在一起存储的。虽然内部会有用两个 table_id 去表示(索引的 data_table_id = 表的 table_id )。OB 是把他们放一起的。这个也决定了 OB 的分布式相比 TiDB (具体是 TiKV)的分布式有更好的控制能力,从而可能有更好的性能。OB 普通表的数据和索引在一起,也就在一个节点,通过索引回表访问数据的时候,不会有跨节点的请求,业务 DML 也不会人为构造分布式事务。

一个分区表,其数据有多个分区,每个分区可以有自己的索引。这个索引如果跟对应的分区在一起(也是两个 table_id 这个没关系),那就是 LOCAL 索引。如果分区表的索引跟对应的分区不在一起,那就是 GLOBAL 索引。这个 GLOBAL 索引默认是一个独立的分区。global 索引容易导致跨机请求或分布式事务,所以最佳实践是优先使用本地索引。

OB 里索引还可以再分区。一个普通表的索引如果设置了分区属性,那么就不能称之为 LOCAL 索引,其索引要跟表数据分开存放。应该就是 GLOBAL 索引。一个分区表的 GLOBAL 索引还可以再次分区,那依然是一个 GLOBAL 索引。

OB 索引如果不指定 LOCAL 或 GLOBAL 关键字,那么默认是什么类型呢?MySQL 租户里默认是 LOCAL 索引,ORACLE 租户里默认是 GLOBAL 索引。所以使用 OB 的时候,建议养成习惯,分区表创建索引的时候 都指定 LOCAL 还是 GLOBAL 。否则,ORACLE 租户一不小心就创建了一个 GLOBAL 索引。

一个普通的表创建普通的索引,尽管指定GLOBAL ,本质上还是LOCAL索引(不过 OB内部元数据里按GLOBAL 索引特点去记录)。这种写法不报语法错误,但不要这么折腾OB。普通表普通索引就LOCAL好了。普通表创建分区索引这种也不报语法错误,但实际上没啥实践意义。一个表都不用分区,索引就更没有分区的必要了。

实验

MySQL [test]> create table t1_nopart(id bigint not null, c1 varchar(50));                                                                                                                                                                                                   
Query OK, 0 rows affected (0.07 sec)

MySQL [test]> create table t1_part(id bigint not null, c1 varchar(50)) partition by hash(id) partitions 2;                                                                                                                                                                  
Query OK, 0 rows affected (0.06 sec)

MySQL [test]> create index t1_nopart_ind1 on t1_nopart(c1) ;                                                                                                                                                                                                          
Query OK, 0 rows affected (0.26 sec)

MySQL [test]> create index t1_part_ind1 on t1_part(c1) ;                                                                                                                                                                                                              
Query OK, 0 rows affected (0.27 sec)

MySQL [test]> create index t1_part_ind2 on t1_part(c1) local;
Query OK, 0 rows affected (0.27 sec)

MySQL [test]> create index t1_nopart_ind2 on t1_nopart(c1,id) partition by hash(id) partitions 2;
Query OK, 0 rows affected (0.27 sec)

obclient [TPCC]> create table t1_nopart(id number, c1 varchar2(50));                                                                                                                                                                                                        
Query OK, 0 rows affected (0.132 sec)

obclient [TPCC]> create table t1_part(id number, c1 varchar2(50)) partition by hash(id) partitions 2;                                                                                                                                                                       
Query OK, 0 rows affected (0.077 sec)


obclient [TPCC]> create index t1_nopart_ind1 on t1_nopart(c1);                                                                                                                                                                                                              
Query OK, 0 rows affected (0.273 sec)

obclient [TPCC]> create index t1_nopart_ind2 on t1_nopart(c1,id) partition by hash(id) partitions 2;
Query OK, 0 rows affected (0.272 sec)

obclient [TPCC]> create index t1_part_ind1 on t1_part(c1) global;                                                                                                                                                                                                           
Query OK, 0 rows affected (0.272 sec)

obclient [TPCC]> create index t1_part_ind2 on t1_part(c1,id) local;
Query OK, 0 rows affected (0.269 sec)

下面 SQL 是在 SYS 租户里观察这个表和索引用的。

select t1.CON_ID,t1.owner,t1.PARTITIONED tbl_partitioned,t1.table_name,t1.status,i1.table_type, i1.index_name, i1.uniqueness, i1.status, i1.partitioned idx_partitioned 
from cdb_tables t1 join cdb_indexes i1 ON ( t1.owner=i1.table_owner and t1.table_name=i1.table_name)
where t1.owner in('test','TPCC') AND t1.table_name IN ('t1_part','t1_nopart')
order by t1.owner,t1.table_name,i1.index_name;

select t1.tenant_id,conv(t1.database_id,10,16) db_id_hex, conv(t1.table_id,10,16) table_id_hex, t1.table_name, t1.index_status,t1.index_type
, conv(t2.table_id,10,16) index_id_hex, t2.table_name index_name, t2.index_status,t2.index_type
from __all_virtual_table t1 left join __all_virtual_table t2 on (t1.tenant_id=t2.tenant_id and t1.table_id=t2.data_table_id)
where t1.tenant_id IN (1002, 1004) AND t1.table_name IN ('t1_part','t1_nopart')
ORDER BY t1.tenant_id, t1.table_name, index_name ;

CON_ID owner tbl_partitioned table_name status table_type index_name uniqueness status idx_partitioned
1,002 test NO t1_nopart VALID TABLE t1_nopart_ind1 NONUNIQUE VALID NO
1,002 test NO t1_nopart VALID TABLE t1_nopart_ind2 NONUNIQUE VALID YES
1,002 test YES t1_part VALID TABLE t1_part_ind1 NONUNIQUE VALID NO
1,002 test YES t1_part VALID TABLE t1_part_ind2 NONUNIQUE VALID YES
1,004 TPCC NO T1_NOPART VALID TABLE T1_NOPART_IND1 NONUNIQUE VALID NO
1,004 TPCC NO T1_NOPART VALID TABLE T1_NOPART_IND2 NONUNIQUE VALID YES
1,004 TPCC YES T1_PART VALID TABLE T1_PART_IND1 NONUNIQUE VALID NO
1,004 TPCC YES T1_PART VALID TABLE T1_PART_IND2 NONUNIQUE VALID YES
tenant_id db_id_hex table_id_hex table_name index_status index_type index_id_hex index_name index_status index_type
1,002 7BDFE 7BE73 t1_nopart 1 0 7BE74 __idx_507507_t1_nopart_ind1 2 1
1,002 7BDFE 7BE73 t1_nopart 1 0 7BE77 __idx_507507_t1_nopart_ind2 2 3
1,002 7BDFE 7BE66 t1_part 1 0 7BE6C __idx_507494_t1_part_ind1 2 3
1,002 7BDFE 7BE66 t1_part 1 0 7BE6F __idx_507494_t1_part_ind2 2 1
1,004 7A160 7A354 T1_NOPART 1 0 7A359 __idx_500564_T1_NOPART_IND1 2 7
1,004 7A160 7A354 T1_NOPART 1 0 7A360 __idx_500564_T1_NOPART_IND2 2 3
1,004 7A160 7A355 T1_PART 1 0 7A35A __idx_500565_T1_PART_IND1 2 3
1,004 7A160 7A355 T1_PART 1 0 7A35D __idx_500565_T1_PART_IND2 2 1

从这些内部表之间的关联关系可以印证上面的理论。内部表 __all_virtual_tableindex_type 这个字段可能是最接近说明一个索引到底是 LOCAL 还是 GLOBAL 的,不过内部表不保证向后兼容,文档也不会说明。

太绕了。如果分析不当,欢迎指出。 :smile:

4 个赞

在OB这种分布式数据库中GLOBAL和LOCAL最大的区别就是索引和主表之间的对应关系,非分区表不管GLOBAL还是LOCAL和主表都是在一起的实际上没区别,官网上的这个帖子也算是回答你的这个问题了

【有问必答】OceanBase 中的全局索引是什么? - 社区问答- OceanBase社区-分布式数据库

3 个赞

个人理解创建一个不指定索引独立分区规则的 global 索引,索引就会被默认的创建为非分区索引(只有一个分区的索引)。

因为主表也是非分区表(只有一个分区的表),表和索引的分区方式一致,都是单分区,所以就等价于创建了一个 local 索引(和主表分区方式一致的索引)。

有点儿绕了,哈哈~

2 个赞

“在 MySQL 模式下创建一个不包含分区的表,并且尝试创建一个带有 GLOBAL 关键字的唯一索引时,实际创建的将是局部索引”,未必吧,哈哈?如果这个唯一索引指定了自己独有的分区方式,应该就还是全局索引了。

小助手要被打屁股了~ @谷渐

2 个赞

老师,那能通过sql脚本判断实际unique key是global的还是local的吗?我的需求是,通过脚本升级客户的数据库,将global变成local。如果已经是local的了,那就不用升级了。

1 个赞

要判断唯一索引(UNIQUE KEY)是全局索引(GLOBAL)还是局部索引(LOCAL),您可以查询 OceanBase 的系统视图来获取索引的类型。对于非分区表,由于它们总是创建局部索引,您只需要确认索引的存在即可。

对于分区表,您可以通过查询 __all_index 视图来获取索引的类型。以下是一个 SQL 脚本示例,用于判断索引是否为局部索引,并根据需要将其转换为局部索引:

步骤 1: 查询索引类型

首先,编写一个 SQL 查询来检查索引的类型。这里我们假设您已经知道表名和索引名。

-- 替换 your_table_name 和 your_index_name 为实际的表名和索引名
SELECT 
  table_name,
  index_name,
  index_type
FROM 
  __all_index
WHERE 
  table_name = 'your_table_name' 
  AND index_name = 'your_index_name';

步骤 2: 判断索引类型并转换

接下来,根据查询结果判断索引类型,并决定是否需要将其转换为局部索引。这里提供一个示例 SQL 脚本,用于实现这一逻辑:

-- 定义变量
DECLARE
  v_table_name VARCHAR2(128);
  v_index_name VARCHAR2(128);
  v_index_type VARCHAR2(128);

BEGIN
  -- 设置表名和索引名
  v_table_name := 'your_table_name';
  v_index_name := 'your_index_name';

  -- 查询索引类型
  SELECT 
    index_type
  INTO 
    v_index_type
  FROM 
    __all_index
  WHERE 
    table_name = v_table_name
    AND index_name = v_index_name;

  -- 判断索引类型
  IF v_index_type = 'GLOBAL' THEN
    -- 如果索引是全局索引,则转换为局部索引
    EXECUTE IMMEDIATE 'ALTER INDEX ' || v_index_name || ' ON ' || v_table_name || ' LOCAL';
  ELSE
    -- 如果索引已经是局部索引,则不需要做任何操作
    DBMS_OUTPUT.PUT_LINE('Index ' || v_index_name || ' on table ' || v_table_name || ' is already LOCAL.');
  END IF;
END;
/

注意事项

  1. 权限: 确保执行此脚本的用户具有足够的权限来查询系统视图和执行 ALTER INDEX 操作。
  2. 表名和索引名: 请确保正确填写 v_table_namev_index_name 变量。
  3. 兼容性: 请确保您的 OceanBase 版本支持这些 SQL 语法。

示例

假设您有一个名为 t1 的表,其中有一个名为 idx_t1_name1 的唯一索引,您想要检查该索引是否为全局索引,并在必要时将其转换为局部索引。您可以按照上述步骤操作。

-- 查询索引类型
SELECT 
  table_name,
  index_name,
  index_type
FROM 
  __all_index
WHERE 
  table_name = 't1' 
  AND index_name = 'idx_t1_name1';

-- 根据查询结果判断并转换索引
DECLARE
  v_table_name VARCHAR2(128);
  v_index_name VARCHAR2(128);
  v_index_type VARCHAR2(128);

BEGIN
  v_table_name := 't1';
  v_index_name := 'idx_t1_name1';

  SELECT 
    index_type
  INTO 
    v_index_type
  FROM 
    __all_index
  WHERE 
    table_name = v_table_name
    AND index_name = v_index_name;

  IF v_index_type = 'GLOBAL' THEN
    EXECUTE IMMEDIATE 'ALTER INDEX ' || v_index_name || ' ON ' || v_table_name || ' LOCAL';
  ELSE
    DBMS_OUTPUT.PUT_LINE('Index ' || v_index_name || ' on table ' || v_table_name || ' is already LOCAL.');
  END IF;
END;
/

这样,您就可以通过 SQL 脚本来判断索引类型,并根据需要将其转换为局部索引。希望这能满足您的需求!如果有其他问题或需要进一步的帮助,请随时告知。

具体信息可参考以下文档:

(小助手的答复已结束,如未能解决您的问题,请继续提问并等待其他同学的回复,谢谢!)

1 个赞

需求是查出 unique global index 是吧?

看了下,可以参考上面 obpilot 这位大佬说的,通过 oceanbase.__all_virtual_table 把 unique global 索引给查出来。

贴一下这条 SQL 吧:
(我这条 SQL 写的不好,没必要用子查询,直接两表 join 下就好了,可以自己调整下~)

obclient [test]> create table t1(c1 int, unique index uk(c1) global) partition by hash(c1);
Query OK, 0 rows affected (0.127 sec)

obclient [test]> select b.table_name data_table_name, index_name from (select data_table_id, SUBSTR(TABLE_NAME, 7 + INSTR(SUBSTR(TABLE_NAME, 7), '_')) index_name, index_type from oceanbase.__all_virtual_table where index_type = 4) a, oceanbase.__all_virtual_table b where a.data_table_id = b.table_id;
+-----------------+------------+
| data_table_name | index_name |
+-----------------+------------+
| t1              | uk         |
+-----------------+------------+
1 row in set (0.042 sec)

其中过滤条件是 index_type = 4 的原因是参考自这个文件 中的 ObIndexType 部分。这个文件中的注释好像还可以解答你上面的问题,哈哈~

如果你曾经可能创建过一些和 local 索引等价的 global 索引,在过滤条件里加上一个 or index_type = 8 就好了~

看代码注释,index_type = 8 的 index 应该就是你上面问的这种会被退化成 local index 的 global index。

4 个赞

膜拜一下庆涛大神的回答!

MySQL 模式下没有合适的字典视图来查索引是 global 的还是 local 的这个问题,和 OB 的产品同学沟通了下。现在直接在 mysql 原生视图例如 statistics 上加字段可能会导致 mysql 生态工具的兼容性问题,后面会继续讨论下是否会增加新视图或平迁 oracle 模式的视图来解决这个问题~

2 个赞

还有您一开始描述的这个问题 ODC 最新版本已经修复了,是老版本 ODC 的一个已知问题,直接 show create table 结果是符合预期的~

2 个赞

这个问题挺有意思,顺手总结成了一篇博客《为什么我创建的 global index 会自动变成 local index?》,放在了《OceanBase 用户问题精粹》这个博客专题里。

3 个赞

原来小助手是机器人,我说咋找半天也找不到这个视图

哈哈,没想到小助手还能帮忙写出 PL 匿名块当做 @AntTech_5N9HRW 需要的 SQL 脚本!

猛一看感觉逻辑大差不差,大概是通过 SQL 查出 index type 是 global 的 index,然后通过执行 alter index 修改索引属性。不过不能直接用,只能简单参考下这个匿名块的逻辑。

老师刚看到,帮大忙了,万分感谢。

哈哈,时隔一个多月的回复,我都快忘了 :joy: