OceanBase详解
# 1、OceanBase介绍
OceanBase是阿里开发的分布式关系型(SQL)数据库,其目标是支持数百TB的数据量以及数十万TPS、数百万QPS的访问量,无论是数据量还是访问量,即使采用非常昂贵的小型机甚至是大型机,单台关系数据库系统都无法承受。
感兴趣的可以去了解一下,OceanBase是一个很不错的国产数据库,毕竟很多大厂都在用。本文只涉及一些关键内容,其他请详细查看官网文档。
# 2、官方常用文档
- OceanBase 数据库社区版部署 (opens new window)
- 内存和超时时间特性差异 (opens new window)
- 基于MySQL的JDBC连接OceanBase (opens new window)
- 基于Oracle的JDBC连接OceanBase (opens new window)
- 数据迁移 (opens new window)
- OceanBase日志 (opens new window)
- 性能调优 (opens new window)
- 查看执行计划 (opens new window)、SQL 执行计划 (opens new window)、理解执行计划 (opens new window)
- SQL规范 (opens new window)
# 3、执行计划
# 3.1、查看执行计划
-- 查询最基本的计划展示
EXPLAIN BASIC 查询SQL
-- 查询详细的计划展示
EXPLAIN EXTENDED 查询SQL
-- 查询整个计划的执行方式,没EXPLAIN EXTENDED详细
EXPLAIN 查询SQL
2
3
4
5
6
EXPLAIN 命令适用于 SELECT、DELETE、INSERT、REPLACE 和 UPDATE 语句,显示优化器所提供的有关语句执行计划的信息,包括如何处理该语句,如何联接表以及以何种顺序联接表等信息。
- EXPLAIN BASIC 案例
执行SQL
explain basic select * from ACCT_LCR where 1=1;
执行计划结果
==================================
|ID|OPERATOR |NAME |
----------------------------------
|0 |EXCHANGE IN REMOTE | |
|1 | EXCHANGE OUT REMOTE| |
|2 | TABLE SCAN |ACCT_LCR|
==================================
Outputs & filters:
-------------------------------------
0 - output([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), filter(nil), startup_filter([1])
1 - output([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), filter(nil)
2 - output([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), filter(nil),
access([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), partitions(p0)
2
3
4
5
6
7
8
9
10
11
12
13
14
- EXPLAIN EXTENDED 案例
执行SQL
explain extended select * from ACCT_LCR where 1=1;
执行计划结果
==================================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
--------------------------------------------------
|0 |EXCHANGE IN REMOTE | |5314 |19674|
|1 | EXCHANGE OUT REMOTE| |5314 |6175 |
|2 | TABLE SCAN |ACCT_LCR|5314 |6175 |
==================================================
Outputs & filters:
-------------------------------------
0 - output([ACCT_LCR.LCR_ID(0xff8671bfd420)], [ACCT_LCR.LCR_CODE(0xff8671c06420)], [ACCT_LCR.LCR_PROJECT_NAME(0xff8671c0f420)], [ACCT_LCR.AMOUNT(0xff8671c18430)], [ACCT_LCR.PERIOD_DATE(0xff8671c20980)], [ACCT_LCR.CREATE_BY(0xff8671c20c40)], [ACCT_LCR.CREATE_TIME(0xff8671c29c40)], [ACCT_LCR.UPDATE_BY(0xff8671c32c40)], [ACCT_LCR.UPDATE_TIME(0xff8671c3bc40)], [ACCT_LCR.REMARK(0xff8671c44c40)], [ACCT_LCR.DEL_FLAG(0xff8671c4dc40)]), filter(nil), startup_filter([1])
1 - output([ACCT_LCR.LCR_ID(0xff8671bfd420)], [ACCT_LCR.LCR_CODE(0xff8671c06420)], [ACCT_LCR.LCR_PROJECT_NAME(0xff8671c0f420)], [ACCT_LCR.AMOUNT(0xff8671c18430)], [ACCT_LCR.PERIOD_DATE(0xff8671c20980)], [ACCT_LCR.CREATE_BY(0xff8671c20c40)], [ACCT_LCR.CREATE_TIME(0xff8671c29c40)], [ACCT_LCR.UPDATE_BY(0xff8671c32c40)], [ACCT_LCR.UPDATE_TIME(0xff8671c3bc40)], [ACCT_LCR.REMARK(0xff8671c44c40)], [ACCT_LCR.DEL_FLAG(0xff8671c4dc40)]), filter(nil)
2 - output([ACCT_LCR.LCR_ID(0xff8671bfd420)], [ACCT_LCR.LCR_CODE(0xff8671c06420)], [ACCT_LCR.LCR_PROJECT_NAME(0xff8671c0f420)], [ACCT_LCR.AMOUNT(0xff8671c18430)], [ACCT_LCR.PERIOD_DATE(0xff8671c20980)], [ACCT_LCR.CREATE_BY(0xff8671c20c40)], [ACCT_LCR.CREATE_TIME(0xff8671c29c40)], [ACCT_LCR.UPDATE_BY(0xff8671c32c40)], [ACCT_LCR.UPDATE_TIME(0xff8671c3bc40)], [ACCT_LCR.REMARK(0xff8671c44c40)], [ACCT_LCR.DEL_FLAG(0xff8671c4dc40)]), filter(nil),
access([ACCT_LCR.LCR_ID(0xff8671bfd420)], [ACCT_LCR.LCR_CODE(0xff8671c06420)], [ACCT_LCR.LCR_PROJECT_NAME(0xff8671c0f420)], [ACCT_LCR.AMOUNT(0xff8671c18430)], [ACCT_LCR.PERIOD_DATE(0xff8671c20980)], [ACCT_LCR.CREATE_BY(0xff8671c20c40)], [ACCT_LCR.CREATE_TIME(0xff8671c29c40)], [ACCT_LCR.UPDATE_BY(0xff8671c32c40)], [ACCT_LCR.UPDATE_TIME(0xff8671c3bc40)], [ACCT_LCR.REMARK(0xff8671c44c40)], [ACCT_LCR.DEL_FLAG(0xff8671c4dc40)]), partitions(p0),
is_index_back=false,
range_key([ACCT_LCR.LCR_ID(0xff8671bfd420)]), range(MIN ; MAX)always true
Used Hint:
-------------------------------------
/*+
*/
Outline Data:
-------------------------------------
/*+
BEGIN_OUTLINE_DATA
FULL(@"SEL$1" "GFKN.ACCT_LCR"@"SEL$1")
END_OUTLINE_DATA
*/
Plan Type:
-------------------------------------
REMOTE
Optimization Info:
-------------------------------------
ACCT_LCR:table_rows:5314, physical_range_rows:5314, logical_range_rows:5314, index_back_rows:0, output_rows:5314, est_method:local_storage, optimization_method=cost_based, avaiable_index_name[ACCT_LCR], estimation info[table_id:1117103813875465, (table_type:1, version:0-1703786410166551-1703786410166551, logical_rc:5314, physical_rc:5314), (table_type:7, version:1703786400441201-1703786410166551-1703786431786655, logical_rc:0, physical_rc:0), (table_type:5, version:1703786400441201-1703786410166551-1703786431786655, logical_rc:0, physical_rc:0), (table_type:0, version:1703786431786655-1703786431786655-9223372036854775807, logical_rc:0, physical_rc:0)]
Parameters
-------------------------------------
{obj:{"INT":1}, accuracy:{length:-1, precision:-1, scale:-1}, flag:0, raw_text_pos:-1, raw_text_len:-1, param_meta:{type:"INT", collation:"binary", coercibility:"NUMERIC"}}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- EXPLAIN案例
执行SQL
explain select * from ACCT_LCR where 1=1;
执行计划结果
==================================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
--------------------------------------------------
|0 |EXCHANGE IN REMOTE | |5314 |19674|
|1 | EXCHANGE OUT REMOTE| |5314 |6175 |
|2 | TABLE SCAN |ACCT_LCR|5314 |6175 |
==================================================
Outputs & filters:
-------------------------------------
0 - output([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), filter(nil), startup_filter([1])
1 - output([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), filter(nil)
2 - output([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), filter(nil),
access([ACCT_LCR.LCR_ID], [ACCT_LCR.LCR_CODE], [ACCT_LCR.LCR_PROJECT_NAME], [ACCT_LCR.AMOUNT], [ACCT_LCR.PERIOD_DATE], [ACCT_LCR.CREATE_BY], [ACCT_LCR.CREATE_TIME], [ACCT_LCR.UPDATE_BY], [ACCT_LCR.UPDATE_TIME], [ACCT_LCR.REMARK], [ACCT_LCR.DEL_FLAG]), partitions(p0)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.2、执行计划形状与算子
OceanBase 数据库的执行计划在内部通常是以树的形式来表示的
分析执行计划
|ID|OPERATOR |NAME |EST. ROWS|COST |
-------------------------------------------------------
|0 |LIMIT | |100 |81141|
|1 | TOP-N SORT | |100 |81127|
|2 | HASH GROUP BY | |2924 |68551|
|3 | HASH JOIN | |2924 |65004|
|4 | SUBPLAN SCAN |VIEW1 |2953 |19070|
|5 | HASH GROUP BY | |2953 |18662|
|6 | NESTED-LOOP JOIN| |2953 |15080|
|7 | TABLE SCAN |ITEM |19 |11841|
|8 | TABLE SCAN |STORE_SALES|161 |73 |
|9 | TABLE SCAN |DT |6088 |29401|
=======================================================
Outputs & filters:
-------------------------------------
0 - output([t1.c1], [t1.c2], [t2.c1], [t2.c2]), filter(nil), sort_keys([t1.c1, ASC], [t1.c2, ASC]), prefix_pos(1)
1 - output([t1.c1], [t1.c2], [t2.c1], [t2.c2]), filter(nil),
equal_conds([t1.c1 = t2.c2]), other_conds(nil)
2 - output([t2.c1], [t2.c2]), filter(nil), sort_keys([t2.c2, ASC])
3 - output([t2.c2], [t2.c1]), filter(nil),
access([t2.c2], [t2.c1]), partitions(p0)
4 - output([t1.c1], [t1.c2]), filter(nil),
access([t1.c1], [t1.c2]), partitions(p0)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
该执行计划分为两个部分:执行计划形状、算子信息。
执行计划形状
- 树的形式来表示,并从叶子节点开始执行
- ID:执行树按照前序遍历的方式得到的编号(从 0 开始)。
- OPERATOR:操作算子的名称。
- NAME:对应表操作的表名(索引名)。
- EST. ROWS:估算该操作算子的输出行数。
- COST:该操作算子的执行代价(微秒)。
查询的计划展示树如下图所示
算子信息
什么是算子?
算子指的是用来执行特定操作的内部逻辑单位。在执行一个查询时,数据库管理系统(DBMS)会生成一棵执行计划树,树上的每一个节点代表一个算子。
Outputs & filters:
部分是各操作算子的详细信息,包括输出表达式、过滤条件、分区信息以及各算子的独有信息(包括排序键、联接键、下压条件等)。示例如下:
Outputs & filters:
-------------------------------------
0 - output([t1.c1], [t1.c2], [t2.c1], [t2.c2]), filter(nil), sort_keys([t1.c1, ASC], [t1.c2, ASC]), prefix_pos(1)
1 - output([t1.c1], [t1.c2], [t2.c1], [t2.c2]), filter(nil),
equal_conds([t1.c1 = t2.c2]), other_conds(nil)
2 - output([t2.c1], [t2.c2]), filter(nil), sort_keys([t2.c2, ASC])
3 - output([t2.c2], [t2.c1]), filter(nil),
access([t2.c2], [t2.c1]), partitions(p0)
4 - output([t1.c1], [t1.c2]), filter(nil),
access([t1.c1], [t1.c2]), partitions(p0)
2
3
4
5
6
7
8
9
10
# 4、理解执行计划
OceanBase 数据库的执行计划在内部通常是以树的形式来表示的,了解执行计划的算子是理解执行计划的关键。如何理解普通索引回表(TABLE SCAN 算子)、全局索引回表(TABLE SCAN 算子)和联接算法(JOIN 算子)等常见的执行计划。
# 4.1、索引回表
对于普通索引和全局索引,索引回表的逻辑都封装在TABLE SCAN 算子中的。在执行计划展示时,会用 is_index_back 标识来表示算子是否需要回表,以及 is_global_index 标识表示算子是否是扫描全局索引。
案例
| ======================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
--------------------------------------
|0 |TABLE SCAN|t1(k1)|100 |12422|
======================================
Outputs & filters:
-------------------------------------
0 - output([t1.c1(0x7f22fbd1e220)], [t1.c2(0x7f227decec40)], [t1.c3(0x7f227decf9b0)], [t1.c4(0x7f22fbd1dfa0)]), filter([t1.c3(0x7f227decf9b0) < 1(0x7f227decf360)], [t1.c4(0x7f22fbd1dfa0) < 1(0x7f22fbd1d950)]),
access([t1.c2(0x7f227decec40)], [t1.c3(0x7f227decf9b0)], [t1.c4(0x7f22fbd1dfa0)], [t1.c1(0x7f22fbd1e220)]), partitions(p0),
is_index_back=true, filter_before_indexback[true,false],
range_key([t1.c2(0x7f227decec40)], [t1.c3(0x7f227decf9b0)], [t1.c1(0x7f22fbd1e220)]),
range(NULL,MAX,MAX ; 1,MIN,MIN),
range_cond([t1.c2(0x7f227decec40) < 1(0x7f227dece5f0)])
2
3
4
5
6
7
8
9
10
11
12
13
14
信息名称 | 含义 |
---|---|
operator | TABLE SCAN 算子的 operator 有两种形式:TABLE SCAN 和 TABLE GET。 TABLE SCAN 属于范围扫描,返回 0 行或者多行数据。 TABLE GET 直接用主键定位,返回 0 行或者 1 行数据。 |
name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。 |
output | 该算子的输出列。 |
filter | 该算子的过滤谓词。没有设置 filter为 nil。 |
partitions | 查询需要扫描的分区。 |
is_index_back | 该算子是否需要回表。 |
is_global_index | 该算子是否是扫描全局索引,包括扫描全局索引表和全局索引回表。 |
filter_before_indexback | 与每个 filter 对应,表明该 filter 是可以直接在索引上进行计算,还是需要索引回表之后才能计算。 |
range_key/range/range_cond | range_key:索引的 rowkey 列。 range:索引开始扫描和结束扫描的位置。判断是否是全表扫描需要关注 range 的范围。例如,对于一个 rowkey 有三列的场景,range(MIN,MIN, MIN ; MAX, MAX, MAX) 代表的就是真正意义上的全表扫描。 range_cond:决定索引开始扫描和结束扫描位置的相关谓词。 |
# 4.2、联接顺序
JOIN 算子用于将两张表的数据,按照特定的条件进行联接。JOIN 的类型主要包括内联接(Inner Join)、外联接(Outer Join)和半联接(Semi/Anti Join)三种。
OceanBase 数据库支持的 JOIN 算子主要有 NESTED LOOP JOIN (NLJ)、MERGE JOIN (MJ) 和 HASH JOIN (HJ)。
# 4.2.1、NESTED LOOP JOIN (NLJ)
| ==========================================
|ID|OPERATOR |NAME|EST. ROWS|COST |
------------------------------------------
|0 |NESTED-LOOP JOIN| |990 |37346|
|1 | TABLE SCAN |T1 |999 |669 |
|2 | TABLE GET |T2 |1 |36 |
==========================================
Outputs & filters:
-------------------------------------
0 - output([T1.C2 + T2.D2]), filter(nil),
conds(nil), nl_params_([T1.C1])
1 - output([T1.C1], [T1.C2]), filter(nil),
access([T1.C1], [T1.C2]), partitions(p0)
2 - output([T2.D2]), filter(nil),
access([T2.D2]), partitions(p0)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
信息名称 | 含义 |
---|---|
output | 该算子输出的表达式。 |
filter | 该算子上的过滤条件。 由于示例中 NLJ 算子没有设置 filter,所以为 nil。 |
conds | 联接条件。 |
nl_params_ | 根据 NLJ 左表的数据产生的下推参数。例如:查询中的 t1.c1。 NLJ 在迭代到左表的每一行时,都会根据 nl_params 构造一个参数,根据这个参数和原始的联接条件 c1 = d1 ,构造一个右表上的过滤条件: d1 = ?。 这个过滤条件会下推到右表上,并抽取索引上的查询范围,即需要扫描索引哪个范围的数据。在查询中,由于存在下推条件 d1 = ?,所以 2 号算子是 TABLE GET 算子。 |
# 4.2.2、MERGE JOIN (MJ)
| ======================================
|ID|OPERATOR |NAME|EST. ROWS|COST |
--------------------------------------
|0 |MERGE JOIN | |3261 |14199|
|1 | SORT | |999 |4505 |
|2 | TABLE SCAN|T1 |999 |669 |
|3 | SORT | |999 |4483 |
|4 | TABLE SCAN|T2 |999 |647 |
======================================
Outputs & filters:
-------------------------------------
0 - output([T1.C2 + T2.D2]), filter(nil),
equal_conds([T1.C2 = T2.D2]), other_conds([T1.C1 + T2.D1 > 10])
1 - output([T1.C2], [T1.C1]), filter(nil), sort_keys([T1.C2, ASC])
2 - output([T1.C2], [T1.C1]), filter(nil),
access([T1.C2], [T1.C1]), partitions(p0)
3 - output([T2.D2], [T2.D1]), filter(nil), sort_keys([T2.D2, ASC])
4 - output([T2.D2], [T2.D1]), filter(nil),
access([T2.D2], [T2.D1]), partitions(p0)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
信息名称 | 含义 |
---|---|
output | 该算子输出的表达式。 |
filter | 该算子上的过滤条件。 由于 MJ 算子没有设置 filter,所以为 nil。 |
equal_conds | 归并联接时使用的等值联接条件,左右子节点的结果集相对于联接列必须是有序的。 |
other_conds | 其他联接条件。 |
# 4.2.3、HASH JOIN (HJ)
| ====================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
------------------------------------
|0 |HASH JOIN | |330 |4850|
|1 | TABLE SCAN|T1 |999 |669 |
|2 | TABLE SCAN|T2 |999 |647 |
====================================
Outputs & filters:
-------------------------------------
0 - output([T1.C2 + T2.D2]), filter(nil),
equal_conds([T1.C1 = T2.D1]), other_conds([T1.C2 + T2.D2 > 1])
1 - output([T1.C1], [T1.C2]), filter(nil),
access([T1.C1], [T1.C2]), partitions(p0)
2 - output([T2.D1], [T2.D2]), filter(nil),
access([T2.D1], [T2.D2]), partitions(p0)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
信息名称 | 含义 |
---|---|
output | 该算子输出的表达式。 |
filter | 该算子上的过滤条件。 由于 HJ 算子没有设置 filter,所以为 nil。 |
equal_conds | 等值联接,左右两侧的联接列会用于计算哈希值。 |
other_conds | 其他联接条件。 |