|
10.2 只读事务8 v! G# l3 @- R: S
只读事务(SELECT语句),经过词法分析、语法分析,预处理后,转化为逻辑
4 k# S/ r$ v, ?1 f7 N查询计划和物理查询计划。以SQL语句select c1,c2 from t1 where id=1 group by c1
5 ]2 L" t& |( F1 Forder by c2为例,MergeServer收到该语句后将调用ObSql类的静态方法4 O7 `, k' p; C( u
direct_execute,执行步骤如下:
( M+ _ g+ _$ x! v; I1 m1)调用flex、bison解析SQL语句生成一个语法树。
+ o& ], a2 F* v7 e7 k( L4 h2)解析语法树,生成逻辑执行计划ObSelectStmt。ObSelectStmt结构中记录了& J6 p: q2 Q6 }1 x& F# }: N: ]
SQL语句扫描的表格名(t1),投影列(c1,c2),过滤条件(id=1),分组列
n9 V/ p2 r/ {+ J- }5 R- q9 y(c1)以及排序列(c2)。
/ q! l; B# a& D% r, w+ w# D3)根据逻辑执行计划生成物理执行计划。ObSelectStmt只是表达了一种意图,7 x5 L8 C* ?' A; _* O" u, O
但并不知道实际如何执行,ObTransformer类的generate_physical_plan将ObSelectStmt转
; R0 l5 [2 _2 p化为物理执行计划。
. }7 x6 _/ T. B$ ^8 }逻辑查询计划的改进以及物理查询计划的选择,即查询优化器,是关系数据库. n9 }) d. @# }( P4 N+ E
最难的部分,OceanBase目前在这一部分的工作不多。因此,本节不会涉及太多关于/ k( J/ @! b) G3 y/ g
如何生成物理查询计划的内容,下面仅以两个例子说明OceanBase的物理查询计划。
7 b. m9 k9 @- {: u; d例10-1 假设有一个单表SQL语句如图10-2所示。
& S2 T) d. x$ _0 [: J& Q3 a0 I+ @* d$ t图 10-2 单表物理查询计划示例
8 O% Y* R1 l: d# P5 i单表SQL语句执行过程如下:9 k' @ D& s# q& B
1)调用TableScan操作符,读取子表t1中的数据,该操作符还将执行投影
! y' A+ W9 `* L4 g(Project)和过滤(Filter),返回的结果只包含c3=10的数据行,且每行只包含c1、
' w. [8 @$ o# r2 Wc2、c3三列。
' e1 s$ |1 q' Q, |$ R8 J& A2 o8 l2)调用HashGroupBy操作符(假设采用基于哈希的分组算法),按照c1对数据 a3 h* m: ^6 z8 o% Q# m
分组,同时计算每个分组内c2列的总和。/ k; w( w; z, L6 D* K2 K$ Z6 N
3)调用Filter操作符,过滤分组后生成的结果,只返回上一层sum(c2)>=10的
" e/ l% u: h) @行。8 ?9 D# |7 a' E9 S' C A y
4)调用Sort操作符将结果按照c1排序。2 k. R1 u$ l9 m
5)调用Project操作符,只返回c1和sum(c2)这两列数据。
3 l4 S# ?7 g" Q6)调用Limit操作符执行分页操作,只返回前20条数据。
9 n% B3 y# b# s! a5 c8 z例10-2 假设有一个需要联表的SQL语句如图10-3所示。" k( h! w5 w! K- F7 o/ L
图 10-3 多表物理查询计划示例
, q" q! b: e8 o7 n( J- n多表SQL语句执行过程如下:/ }! h) }& o0 M, j
1)调用TableScan分别读取t1和t2的数据。对于t1,使用条件c3=10对结果进行过 |: t" ] J% X. [; B- s
滤,t1和t2都只需要返回c1,c2,c3这三列数据。
/ m0 L. y- s6 x% r2)假设采用基于排序的表连接算法,t1和t2分别按照t1.c2和t2.c2排序后,调用
6 _% t! `& R( \7 w/ L1 TMerge Join运算符,以t1.c2=t2.c2为条件执行等值连接。
" V/ @8 W8 \ l" R7 Q+ f" v3)调用HashGroupBy运算符(假设采用基于哈希的分组算法),按照t1.c1对数
7 t, r' O2 r# L% ?据分组,同时计算每个分组内t2.c3列的总和。
! F' h+ W# b. B! V! Z/ {8 H4)调用Filter运算符,过滤分组后的生成的结果,只返回上一层sum(t2.c3)>' E& A. w% P" _- n+ V I
=10的行。& z2 u5 }5 b% G5 w/ A
5)调用Sort操作符将结果按照t1.c1排序。* W3 J4 M: D( t* _3 S* d
6)调用Project操作符,只返回t1.c1和sum(t2.c3)这两列数据。
! h4 n' t6 a0 s5 D9 T& y4 Y8 c; m7)调用Limit操作符执行分页操作,只返回前20条数据。
9 s+ Y4 i. h/ ~) f2 `( z10.2.1 物理操作符接口
: i) K& B8 D+ l9 k; C! R; o/ s9.4.2节介绍一期分布式存储引擎中的迭代器接口为ObIterator,通过它,可以将" k7 N1 \# B" R: `
读到的数据以cell为单位逐个迭代出来。然而,数据库操作总是以行为单位的,因
* V9 N. r7 T: V此,二期实现数据库功能层时考虑将基于cell的迭代器修改为基于行的迭代器。
% S) z7 x6 v, M2 Q; y/ H行迭代器接口如下:, t5 S7 i/ [; ]8 o X) n
//ObRow表示一行数据内容
7 v4 `5 k+ U! w& v) y) Wclass ObRow
, M; m L# K( k+ i" u4 m{* f, R, Y! O# H) G: ~+ o4 o
public:
F( a& b, i4 |//根据表ID以及列ID获得指定cell
6 B, X4 u, r$ e7 I# M" |//@param[in]table_id表格ID; M& D' I! Z$ R( d
//@param[in]column_id列ID! t# W. v2 f( ?3 d4 u. X
//@param[out]cell读到的cell
5 G j) X) U- fint get_cell(const uint64_t table_id,const uint64_t column_id,ObObj*&cell);" L. k( F0 m/ x& G
//获取第cell_idx个cell0 ~/ `+ `1 M0 @ J1 d# E
int raw_get_cell(const int64_t cell_idx,const ObObj*&cell,uint64_t&table_id,5 y8 v/ k6 y1 m) Q5 h$ f! i
uint64_t&column_id);) s9 B0 u* P( U" Q+ w X
//获取本行的列数, i% a0 V, K& a$ [! v+ j9 f% n2 n* e
int64_t get_column_num()const;9 c% B! f9 i4 i0 \
};5 n7 C4 p5 e K3 D: q$ l4 O
每一行数据(ObRow)包括多个列,每个列的内容包括所在的表) u7 y3 d# _- H) P* s
ID(table_id),列ID(column_id)以及列内容(cell)。ObRow提供两种访问方
+ Q u! i1 ?1 b- x# g式:根据table_id和column_id随机访问某个列,以及根据列下标(cell_idx)获取某个! T; m( {1 J( F' ~7 }
指定列。9 I' e: Q1 K7 ]! F. F( p( z2 _
物理运算符接口如下:
9 v3 j3 z. @9 c! Q/ r" P; D& n8 I1 V8 x//物理运算符接口& i: u% ~. p% m$ N
class ObPhyOperator
% ]0 d, e) d4 x+ r& [# \0 X{
2 i: y4 |( g' i3 A! M8 l1 {public:/ }4 r( f' e3 i! `9 g- y
//添加子运算符,所有非叶子节点物理运算符都需要调用该接口: M: f6 z {0 S* _* J
virtual int set_child(int32_t child_idx,ObPhyOperator&child_operator);
8 h- C4 e. c' ]3 W5 v0 j//打开物理运算符。申请资源,打开子运算符等
" u( ]1 H. O w5 m+ g6 {: {virtual int open()=0;
: v6 X$ i4 J1 C//关闭物理运算符。释放资源,关闭子运算符等
0 r- ]2 U( s' z+ Z2 f( jvirtual int close()=0;) R$ c, d5 S& `) x& V, I' j) t
//获得下一行数据内容6 z y `# h9 u$ R2 V5 V/ N
//@param[out]row下一行数据内容的引用& i/ l2 ~3 [7 j& y. X+ J+ v0 B
//@return返回码,包括成功、迭代过程中出现错误以及迭代完成
t0 U. D( M, L. b; S4 Hvirtual int get_next_row(const ObRow*&row)=0;# L& |! Y7 w+ P: k
};
3 H$ Y' @4 f5 _6 c7 YObPhyOperator每次获取一行数据,使用方法如下:
5 S/ k& W9 @# d8 j* U& Z/ w( BObPhyOperator root_operator=root_operator_;//根运算符
. c8 N+ H! b6 G% v- C9 Rroot_operator->open();- y8 ^! N8 s5 B' @
ObRow*row=NULL;
4 S3 w& j9 N4 gwhile(OB_SUCCESS==root_operator->get_next_row(row))5 Z& ]9 T# X% q$ {
{
8 S+ y) R) j6 j( j. t u) f- \& }7 gOutput(row);//输出本行2 E4 C5 x7 e6 }0 D. p0 V8 P6 J
}
0 G9 c3 |3 O; o* M; Oroot_operator->close();* v: R7 a# _* D' C3 E
为什么ObPhyOperator类中有一个set_child接口呢?这是因为所有的物理运算符构/ s- G! G& V r; l
成一个树,每个物理运算的输出结果都可以认为是一个临时的二维表,树中孩子节/ F: `7 V6 S9 Q8 i2 y
点的输出总是作为它的父亲节点的输入。例10-1中,叶子节点为一个TableScan类型% O; s& L* x0 ^& D
的物理运算符(称为table_scan_op),它的父亲节点为一个HashGroupBy类型的物理
$ X2 S& g+ y* e1 D9 r运算符(称为hash_group_by_op),接下来依次为Filter类型物理运算符filter_op,Sort
( U) Z5 ?, s1 y; a8 \类型物理运算符sort_op,Project类型物理运算符project_op,Limit类型物理运算符$ G. o0 W. T8 y& f1 \5 r" x) t
limit_op。其中,limit_op为根运算符。那么,生成物理运算符时将执行如下语句:" y" `; j9 E6 C! y9 e5 P
limit_op->set_child(0,project_op);
3 k) H" h0 M# c" e! L% l2 Dproject_op->set_child(0,sort_op);% u' R2 Q. h: j0 b7 G* O% ]
sort_op->set_child(0,filter_op);
, G1 x0 }2 g9 W1 }filter_op->set_child(0,hash_group_by_op);1 {0 U8 e, O8 m3 ]
hash_group_by_op->set_child(0,table_scan_op);
1 U& E; H# p8 F/ x; y V) croot_op=limit_op;; `& \: Z4 F" I5 Q8 `9 o
SQL最终执行时,只需要迭代root_op(即limit_op)就能够把需要的数据依次迭
2 I9 F! t% ~2 Y4 N* h3 U代出来。limit_op发现前一批数据迭代完成则驱动下层的project_op获取下一批数据,0 g' y! W# n h% l4 _4 v+ n( ?5 v
project_op发现前一批数据迭代完成则驱动下层的sort_op获取下一批数据。以此类
2 X& q' ^6 d, D0 M* W9 @( k0 h推,直到最底层的table_scan_op不断地从原始表t1中读取数据。8 X/ m1 W0 h8 H+ _0 W
10.2.2 单表操作
3 L2 S- d4 O: Q5 c单表相关的物理运算符包括:
0 N' B" g: C# v! ^4 b V8 @●TableScan:扫描某个表格,MergeServer将扫描请求发给请求的各个子表所在
- i$ e; \) \7 ~( x" y. ]# f6 W的ChunkServer,并将ChunkServer返回的结果按照子表范围拼接起来作为输出。如果
a7 r, }1 ^. M请求涉及多个子表,TabletScan可由多台ChunkServer并发执行。
/ H! v8 a; g2 v7 a●Filter:针对每行数据,判断是否满足过滤条件。. K1 R! @2 v+ \. n+ q" N3 j
●Projection:对输入的每一行,根据定义的输出表达式,计算输出结果行。8 X' x( H6 b3 |8 \1 z" x. Q
●GroupBy:把输入数据按照指定列进行聚集,对聚集后的每组数据可以执行计4 d- ^0 b1 w! ~3 A
数(count)、求和(sum)、计算最小值(min)、计算最大值(max)、计算平均值
s# [" m% e$ e* Q m* w(avg)等聚集操作。) n/ T0 ~. ^; U4 G
●Sort:对输入数据进行整体排序,如果内存不够,需要使用外排序。- g, ~# z# P! Y8 {* A7 C f- s9 {
●Limit(offset,count):返回行号在[offset,offset+count)范围内的行。, {8 T# q2 Q# d0 ]
●Distinct:消除某些列相同的重复行。
' p3 b/ e; N- b! K! oGroupBy、Distinct物理操作符可以通过基于排序的算法实现,也可以通过基于哈
) _9 _& p- @+ e1 V |5 @希的算法实现,分别对应HashGroupBy和MergeGroupBy,以及HashDistinct和- @6 H; c e f( V9 Y/ F. ]
MergeDistinct。下面分别讨论排序算法和哈希算法。
- u* v+ y7 b* L! C/ \1.排序算法
& }4 m; n. Q; m# y- S2 X, fMergeGroupBy、MergeDistinct以及Sort都需要使用排序算法。通用的<key,value* S+ ^) n' s( [2 M; P- t! c+ H
>排序器可以分为两个阶段:1 j3 t1 o8 D0 v4 w* j& X" X0 s, r
●数据收集:在数据收集阶段,调用者将<key,value>对依次加入到排序器。如9 T4 y2 M$ f/ F! F
果数据总量超过排序器的内存上限,需要首先将内存中的数据排好序,并存储到外! p4 [) E- K* J7 W' h4 c# d
部磁盘中。
' O1 h* d7 F! n# ]6 E) m" T●迭代输出:迭代第一行数据时,内存中可能有一部分未排序的数据,磁盘中也; l2 z { i) }6 y6 ?
可能有几路已经排好序的数据。因此,首先将内存中的数据排好序。如果数据总量
f; s, E1 t9 E" Z. M( w6 I" v不超过排序器内存上限,那么将内存中已经排好序的数据按行迭代输出(内排
% X% q* W6 g) E; s; |序);否则,对内存和磁盘中的部分有序数据执行多路归并,一边归并一边将结果
0 z4 ?6 N: C1 \' s8 Z迭代输出。
9 Y @4 d# q# x. l2.哈希算法' J7 E& \% S" P$ s; ^8 @" M8 U
HashGroupBy以及HashDistinct都需要使用哈希算法。假设需要对<key,value>对
( ^( ]: @! G% g: u/ }9 t6 v按照key分组,那么首先使用key计算哈希值K,并将这个<key,value>对写入到第K个
: g3 Q4 E1 A: j$ _( v8 |- H7 @桶中。不同的key可能对应相同的哈希桶,因此,还需要对每个哈希桶内的<4 L6 o. _9 X$ c. I- R: v
key,value>对排序,这样才能使得key相同的元组能够连续迭代出来。哈希算法的难
- l6 q9 l' f6 b5 q! A, l点在于数据总量超过内存上限的处理,由于篇幅有限,请自行思考。6 j( q6 t _* M% C1 a( ?
10.2.3 多表操作! P# C* J+ X( k
多表相关的物理操作符主要是Join。最为常见的Join类型包括两种:内连接
" m0 q. w/ d1 R0 s% Y8 Z/ @(Inner Join)和左外连接(Left Outer Join),而且基本都是等值连接。如果需要连接
4 u* ^: K7 I/ N: z! Y% }2 _多张表,可以先连接前两张表,再将前两张表连接生成的结果(相当于一张临时- R) z% D4 h8 ?$ D
表)与第三张表格连接,以此类推。2 ^) P" ^( {5 b% |' b6 K6 L
两张表实现等值连接方式主要分为两类:基于排序的算法(MergeJoin)以及基+ e- F: v9 F" G" h5 @' O& X9 I, b5 ]% H
于哈希的算法(HashJoin)。对于MergeJoin,首先使用Sort运算符分别对输入表格预
2 h- W! ^8 U/ z4 j9 d" i4 S处理,使得两张输入表都在连接列上排好序,接着按顺序迭代两张输入表,合并连 c- ~9 k, W* p i
接列相同的行并输出;对于HashJoin,首先根据连接列计算哈希值K,并分别将两张
7 j5 G, m) S$ H, Q! ~+ S" m输入表格的数据写入到第K个桶中。接着,对每个哈希桶按照连接列排序。最后,依, O; V: I' }, h
次对每个哈希桶合并连接列相同的行并输出。
9 w& A$ t7 K- p: Z子查询分为两种:关联子查询和非关联子查询,其中比较常用的是使用IN子句
% W. ~5 O1 J4 `% K. q, W8 I8 p; _2 G的非关联子查询。举例如下:1 I. t1 k3 c( Q# n! Y2 o: A
例10-3 假设有两张表格:item(商品表,包括商品号item_id,商品名7 b% W! g# B( V
item_name,分类号category_id,),category(类别表,包括分类号category_id,分
9 I2 B9 O1 p* @6 H2 v- e& J类名category_name)。如果需要查询分类号出现在category表中商品,可以采用图10-
; X2 @: b% X3 o/ r3 X4左边的IN子查询,而这个子查询将被自动转化为图10-4右边的等值连接。如果
G: T7 i0 d! }. O- F( [- Q+ M8 ?category表中的category_id列有重复,表连接之前还需要使用distinct运算符来删除重
1 c6 X; _) y) A! l" {复的记录。
9 y( Q V4 k5 p0 p1 d4 R0 p0 m图 10-4 IN子查询转化为等值连接
/ _0 @, N9 D8 I& }' V7 D6 Q例10-4 例10-3中,如果category表只包含category_id为1~10的记录,那么,可1 g3 ?2 C8 L; U/ E) z& Z% y& n2 r
以将IN子查询写成图10-5中的常量表达式。' K1 F/ g& c. l
图 10-5 IN子查询转化为常量表达式, Q: _3 q$ y6 S- `$ K/ [! f. h L( P/ x
转化为常量表达式后,MergeServer执行SQL计算时,可以将IN后面的常量列表. h' {) Q4 S7 z4 W
发送给ChunkServer,ChunkServer只返回category_id在常量列表中的商品记录,而不是( H# Y9 i& i' U( i# i) F
将所有的记录返回给MergeServer过滤,从而减少二者之间传输的数据量。
- b( r1 N8 O# E. B6 l0 N) ~8 gOceanBase多表操作做得还很粗糙,例如不支持嵌套连接(Nested Loop Join), u9 H) z$ v# S
不支持非等值连接,不支持查询优化等,后续将在合适的时间对这一部分代码进行& i4 o, ^9 y+ Y# I9 A6 j; u7 m H9 K
重构。
' Z$ |, K/ o" K% G10.2.4 SQL执行本地化
4 \. b$ h+ v" j7 }& n) a5 c- mMergeServer包含SQL执行模块MS-SQL,ChunkServer也包含SQL执行模块CS-
- Q7 `; `' |, n1 W% z) rSQL,那么,如何区分二者的功能呢?多表操作由MergeServer执行,对于单表操
* K( k* u! t2 t/ R作,OceanBase设计的基本原则是尽量支持SQL计算本地化,保持数据节点与计算节
- y* ?9 B: {9 X6 N2 X9 ~$ K' \$ q点一致,也就是说,只要ChunkServer能够实现的操作,原则上都应该由它来完成。' b; @# O0 h1 y* b4 `
●TableScan:每个ChunkServer扫描各自子表范围内的数据,由MergeServer合并8 b/ l' D6 N. W1 Z+ ~; B1 n5 @5 W" V- i
ChunkServer返回的部分结果。5 w y) A" ?1 r4 S' G% ~: S
●Filter:对基本表的过滤集成在TableScan操作符中,由ChunkServer完成。对分& k% C. r: q5 g0 x2 c/ c& I* z
组后的结果执行过滤(Having)集成在GroupBy操作符中,一般情况下由MergeServer
3 j+ B M6 m. G l( z% B完成;但是,如果能够确定每个分组的所有数据行只属于同一个子表,比如SQL请求% b0 S* V# U3 [1 K0 h* t; C
只涉及一个tablet,那么,分组以及分组后的过滤操作符可以由ChunkServer完成。, l1 ^8 W" m$ ?9 f x4 U! E$ S
●Projection:对基本表的投影集成在TableScan操作符中,由ChunkServer完成,
\+ z5 N4 g3 f; n& g$ H对最终结果的投影由MergeServer完成。5 O8 N- \9 o+ Z5 V, t1 l
●GroupBy:如果SQL读取的数据只在一个子表上,那么由该子表所在的
5 m# U c6 O% c. ^" G& xChunkServer完成分组操作;否则,每台ChunkServer各自完成部分数据的分组操作,' W! g, h( x' k8 n, `& O. _0 h# [
执行聚合运算后得到部分结果,再由MergeServer合并所有ChunkServer返回的部分结
9 e& ~6 y7 H% [0 m3 J果,对于属于同一个分组的数据再次执行聚合运算。某些聚合运算需要做特殊处+ _; ~. D: ~7 y+ Z/ ^
理,比如avg,需要转化为sum和count操作发送给ChunkServer,MergeServer合并* f2 e2 E3 Z1 Y( m
ChunkServer返回的部分结果后计算出最终的sum和count值,并通过sum/count得到avg# k; i; p# |( G& ^/ B& L4 Z: J
的最终结果。- Y H2 L3 J& F" S! J4 a/ E, s
●Sort:如果SQL读取的数据只在一个子表上,那么由该子表所在的ChunkServer7 k! A/ c4 I' N
完成排序操作;否则,每台ChunkServer各自完成部分数据的排序,并将排好序的部; T+ A/ N7 r* \, p: K
分数据返回MergeServer,再由MergeServer执行多路归并。5 O6 w3 _" o9 b
●Limit:Limit操作一般由MergeServer完成,但是,如果请求的数据只在一个子4 r7 I$ _: q( O
表上,可以由ChunkServer完成,这往往会大大减少MergeServer与ChunkServer之间传
7 J7 T* U6 U1 j2 g输的数据量。
8 G! Q7 d) B8 Y: @3 b●Distinct:Distinct与GroupBy类似。ChunkServer先完成部分数据的去重,再由
' d7 Y6 ^" O' c3 c# @- mMergeServer进行整体去重。5 I9 X5 M0 M) r$ [! |9 c
例10-5 图10-2中的SQL语句为"select c1,sum(c2)from t1 where c3=10 group
! a' q4 x8 a$ W, q0 c' o# K& a$ yby c1 having sum(c2)>=10 order by c1 limit 0,20"。执行步骤如下:7 l# Q, c9 `6 E# n% y% U' h$ [
1)ChunkServer调用TableScan操作符,读取子表t1中的数据,该操作符还将执行
' j+ V6 N6 Q4 j4 {- n投影(Project)和过滤(Filter),返回的结果只包含c3=10的数据行,且每行只包含
8 p9 r2 H% ~ U% w% M$ Zc1、c2、c3三列。
: N6 S5 N0 P; ^; O& z2)ChunkServer调用HashGroupBy操作符(假设采用基于哈希的分组算法),按) m$ a) Z* f; |: m. U9 f8 ?& X3 A
照c1对数据分组,同时计算每个分组内c2列的总和sum(c2)。' R# c. C2 Y; w0 i
3)每个ChunkServer将分组后的部分结果返回MergeServer,MergeServer将来自不4 V* ^, S0 d6 E6 S, @+ R
同ChunkServer的c1列相同的行合并在一起,再次执行sum运算。5 P2 U% k5 D3 @; k% f
4)MergeServer调用Filter操作符,过滤第3)步生成的最终结果,只返回
, G# p2 l7 h! a" R+ Nsum(c2)>=10的行。
) n# U! }1 G! j7 @5)MergeServer调用Sort操作符将结果按照c1排序。
" T7 _4 u& a' ]+ K8 W N# |6 Q) {6)MergeServer调用Project操作符,只返回c1和sum(c2)这两列数据。+ G Y2 L( s" S
7)MergeServer调用Limit操作符执行分页操作,只返回前20条数据。
2 F$ |/ J, N$ k当然,如果能够确定请求的数据全部属于同一个子表,那么,所有的物理运算
0 l$ t5 o2 b- R- x$ @# z) T& i- \$ B3 [符都可以由ChunkServer执行,MergeServer只需要将ChunkServer计算得到的结果转发* U* c6 X, d a- J2 @0 a1 R
给客户端。1 Q! {& S4 S4 D! n3 |
) b9 Y7 Z# Q) q- B3 o3 T- Q
" ~% f7 y9 b# T% v2 E, H; e+ a
|
|