【OceanBase】全局索引与局部索引管理

1.主键和二级索引

◼ 主表
指使用CREATE TABLE语句创建的表对象。也是索引对象所依赖的表(即CREATE INDEX语句中ON子句所指定的表)
◼ 主键
OceanBase 的每一张表都有主键,并在内部以主键为序组织数据。如果在创建用户表时不显式指定主键,系统会自
动为表生成隐藏主键,隐藏主键不可被查询
◼ 索引(索引表)
指使用CREATE INDEX语句创建的索引对象。有时为了便于大家理解,也会把索引对象类比为一个表对象,即索引表

我们会以一个实际的数据库表来演示各种情况,这
个表的名字叫employee(员工信息表),表的结构如下:
employee
{
emp_id, /* 员工ID */
emp_name /* 员工名字 */
dpet_id, /* 部门ID */
...
}

2.传统“非分区表”中主表和索引的关系

传统的“非”分区表中,主表和索引的对应关系:
主表的所有数据都保存在一个完整的数据结构中,主表上的每一个索引也对应一个完整的数据
结构(比如最常见的B+ Tree),主表的数据结构和索引的数据结构之间是一对一的关系,
如下图所展示,在 employee表中,以 emp_id创建的索引:
idx_emp_id on employee (emp_id)

3.局部索引与全局索引

当分区表出现之后,情况发生了变化:主表的数据按照分区键(Partitioning Key)的值被分成了多个分区,
每个分区都是独立的数据结构,分区之间的数据没有交集。这样一来,索引所依赖的单一数据结构不复存在,
那索引需要如何应对呢?——这就引入了“局部索引”和“全局索引”两个概念。

◼ 局部索引
局部索引又名分区索引,创建索引的分区关键字是LOCAL,分区键等同于表的分区键,分区数等同于表的分区数,
总之,局部索引的分区机制和表的分区机制一样
◼ 全局索引
全局索引的创建规则是在索引属性中指定GLOBAL关键字,与局部索引相比,全局索引最大的特点是全局
索引的分区规则跟表分区是相互独立的,全局索引允许指定自己的分区规则和分区个数,
不一定需要跟表分区规则保持一致

4.局部索引

分区表的局部索引和非分区表的索引类似,索引的数据结构还是和主表的数据结构保持一对一的关系,
但由于主表已经做了分区,主表的“每一个分区”都会有自己单独的索引数据结构。

5.全局索引-全局非分区索引

分区表的全局索引不再和主表的分区保持一对一的关系,而是将所有主表分区的数据合成一个整体来建立全局索引。
更进一步,全局索引可以定义自己独立的数据分布模式,既可以选择非分区模式也可以选择分区模式 :
◼ 全局非分区索引(Global Non-Partitioned Index)
◼ 全局分区索引(Global Partitioned Index)

6.全局索引-全局分区索引

分区表的全局索引不再和主表的分区保持一对一的关系,而是将所有主表分区的数据合成一个整体来建立全局索引。
更进一步,全局索引可以定义自己独立的数据分布模式,既可以选择非分区模式也可以选择分区模式 :
◼ 全局非分区索引(Global Non-Partitioned Index)
◼ 全局分区索引(Global Partitioned Index)

7.功能需求:在表的’分区键无关’的字段上建唯一索引

局部索引在“索引键没有包含主表所有的分区键字段”的情况下,此时索引键值对应的索引数据在所有分区中都可能存
在。如下图,employee按照emp_id做了分区,但同时想利用局部索引建立关于emp_name的唯一约束是无法实现的。
由于某索引键值在所有分区的局部索引上都可能存在,索引扫描必须在所有的分区上都做一遍,以免造成数据遗漏。
这会导致索引扫描效率低下,并且会在全局范围内造成CPU和IO资源的浪费

8.全局非分区索引与全局分区索引的比较

全局索引的分区键一定是索引键的前缀
◼ 全局非分区索引 :
此时索引的结构和“非分区”表没有区别,只有一个完整的索引树,自然保证唯一性。
并且只有一个完整的索引树,自然没有多分区扫描的问题
◼ 全局分区索引 :
数据只可能落在一个固定的索引分区中,因此每个索引分区内保证唯一性约束,就能在全表范围内保证唯一性约束
全局索引能保证某一个索引键的数据只落在一个固定的索引分区中 ,所以无论是针对固定键值的索引扫描,还是针
对一个键值范围的索引扫描,都可以直接定位出需要扫描的一个或者几个分区

9.局部索引与全局索引的执行计划的比较

局部索引
create table t_p_hash (c1 varchar(20),c2 int, c3 varchar(20)) partition by hash(c2) partitions 3;
create index idx_t_p_hash_c1 on t_p_hash (c1) local;
create index idx_t_p_hash_c3 on t_p_hash (c3) local;
explain extended select c1,c2 from t_p_hash where c3='100';
|ID|OPERATOR |NAME |EST. ROWS|COST |
----------------------------------------------------------------------
|0 |PX COORDINATOR         |         |2970 |19207|
|1 | EXCHANGE OUT DISTR    |:EX10000 |2970 |18364|
|2 |  PX PARTITION ITERATOR|         |2970 |18364|
|3 |   TABLE SCAN          |t_p_hash(idx_t_p_hash_c3)|2970 |18364|
======================================================================
Outputs & filters: 
-------------------------------------
0 - output([t_p_hash.c1(0x7f8b99cb4370)], [t_p_hash.c2(0x7f8b99cb30a0)]), filter(nil)
1 - output([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), filter(nil), dop=1
2 - output([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), filter(nil)
3 - output([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), filter(nil), 
access([t_p_hash.c2(0x7f8b99cb30a0)], [t_p_hash.c1(0x7f8b99cb4370)]), partitions(p[0-2]), 
is_index_back=true, range_key([t_p_hash.c3(0x7f8b99cb3e50)], [t_p_hash.c2(0x7f8b99cb30a0)],
[t_p_hash.__pk_increment(0x7f8b99cd9660)]), range(100,MIN,MIN ; 100,MAX,MAX), 
range_cond([t_p_hash.c3(0x7f8b99cb3e50) = '100'(0x7f8b99cb37f0)])



索引的回表逻辑是封装在 table scan 算子中:
is_index_back=true
TABLE SCAN
t_p_hash(idx_t_p_hash_c3)


c3='100'
partitions(p[0-2])
查询中如果没有指定分区键,那么局部索引将无法进行分区裁剪.分区键是C2;

--全局索引
create table t_p_key (c1 varchar(20),c2 int,c3 varchar(20)) partition by key (c2) partitions 3;
create unique index idx_t_p_key_c3_g on t_p_key (c3) global partition by key (c3) partitions 3;
explain extended select c1,c2 from t_p_key where c3='66';

|ID|OPERATOR |NAME |EST. ROWS|COST|
----------------------------------------------------------
|0 |TABLE LOOKUP|t_p_key                  |1 |91 |
|1 | TABLE SCAN |t_p_key(idx_t_p_key_c3_g)|1 |36 |
==========================================================
Outputs & filters: 
-------------------------------------
0 - output([t_p_key.c1(0x7f8b99cb6050)], [t_p_key.c2(0x7f8b99cb3a80)]), filter(nil), 
partitions(p[0-2])
1 - output([t_p_key.c2(0x7f8b99cb3a80)], [t_p_key.__pk_increment(0x7f8b99cd9660)]), filter(nil), 
access([t_p_key.c2(0x7f8b99cb3a80)], [t_p_key.__pk_increment(0x7f8b99cd9660)]), 
partitions(p2),
is_index_back=false,
range_key([t_p_key.c3(0x7f8b99cb5010)], [t_p_key.shadow_pk_0(0x7f8b99cda660)], 
[t_p_key.shadow_pk_1(0x7f8b99cda8f0)]), range(66,MIN,MIN ; 66,MAX,MAX), 
range_cond([t_p_key.c3(0x7f8b99cb5010) = '66'(0x7f8b99cb5760)])



1).通过WHERE 条件裁剪出全局索引的分区 p2
partitions(p2)
where c3='66'

2).对全局索引进行 table scan 
操作得到对应的主键
TABLE SCAN 
t_p_key(idx_t_p_key_c3_g)


3).利用 table lookup 算子对主表进行精确的分区扫描,以避免扫描主表的所有分区
TABLE LOOKUP

10.局部索引与全局索引的取舍

◼ 如果查询条件里“包含完整的分区键”,使用本地索引是最高效的
◼ 如果需要“不包含完整分区键”的唯一约束:
⚫ 用全局索引
⚫ 或者本地索引,且需要索引列上必须带上表的分区键
◼ 其它情况,case by case:
⚫ 通常来说,全局索引能为高频且精准命中的查询(比如单记录查询)提速并减少IO;对范围查询则不一定哪种索
引效果更好
⚫ 不能忽视全局索引在DML语句中引入的额外开销:数据更新时带来的跨机分布式事务,事务的数据量越大则分布
式事务越复杂
◼ 如果数据量较大,或者容易出现索引热点,可考虑创建全局分区索引

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值