注册
DM7操作符优化系列(一)—— 单表操作符

DM7操作符优化系列(一)—— 单表操作符

Grrr 2020/11/23 3217 18 0
摘要 本系列主要讲解SQL优化过程中关于操作符的基础知识。本篇文章通过具体的示例语句和对应的执行计划,对五种单表操作符的含义进行了诠释。

1、操作符是什么

操作符是SQL执行的基本单元,所有的SQL语句最终都是转换成一连串的操作符最后在服务器上执行,得到需要的结果,因此了解操作符也是读懂执行计划的基础。

2、存储方式介绍

在介绍操作符之前,先介绍一些简单结构的存储方式,以便理解操作符的作用方式:

a.一般普通表的物理存储:
(CREATE TABLE T1(C1 INT,C2 INT))
ROWID,C1,C2 ROWID,C1,C2 ROWID,C1,C2 …
这种方式进行存储,这里ROWID我们称之为聚簇KEY,磁盘上面的数据按照ROWID顺序存储,每一行存储着表的完整数据

b.二级索引的存储:
(CREATE INDEX I_TEST1 ON TABLE(C1))
C1,ROWID C1,ROWID C1,ROWID C1,ROWID …
可以看到,一般的二级索引不存储表的所有数据,仅按C1的顺序存储数据,但是附加存储了C1对应行的ROWID,通过ROWID,我们可以去基表上拿到整行的数据

c.聚簇索引的存储:
(CREAT CLUSTER INDEX I_TEST2 ON TABLE(C1))
C1,C2,ROWID C1,C2,ROWID C1,C2,ROWID …
这里同样是按C1的顺序存储数据,与二级索引不同的是保留了整行数据

3、单行操作符及实例

最基础的操作符是单行操作符,包括:
CSCN:基础全表扫描(a),从头到尾,全部扫描
SSCN:二级索引扫描(b),从头到尾,全部扫描
SSEK:二级索引范围扫描(b),通过键值精准定位到范围或者单值
CSEK:聚簇索引范围扫描(c),通过键值精准定位到范围或者单值
BLKUP:根据二级索引的ROWID,回原表中取出全部数据(b + a)

3.1 CSCN基础全表扫描

首先创建测试环境:创建表T1并录入数据,相关SQL语句如下

CREATE TABLE T1(C1 INT,C2 INT); INSERT INTO T1 SELECT LEVEL,LEVEL FROM DUAL CONNECT BY LEVEL < 10000; COMMIT;

进行查询

SELECT * FROM T1 WHERE C1 = 5;

梁扬文章.png
接下来我们看看此次查询的执行计划

--SEL1 EXPLAIN SELECT * FROM T1 WHERE C1 = 5; 1 #NSET2: [1, 249, 16] 2 #PRJT2: [1, 249, 16]; exp_num(3), is_atom(FALSE) 3 #SLCT2: [1, 249, 16]; T1.C1 = 5 4 #CSCN2: [1, 9999, 16]; INDEX33555446(T1)

在这个场景中我们创建了一个普通表,没有任何索引、过滤,那么从T1中取出数据只能走全表扫描CSCN。

3.2 SSCN 二级索引扫描

下面我们在测试表中创建一条索引

CREATE INDEX I_TEST1 ON T1(C1);

再看下面两条语句的执行计划

--SEL2 EXPLAIN SELECT C1 FROM T1; 1 #NSET2: [1, 9999, 12] 2 #PRJT2: [1, 9999, 12]; exp_num(2), is_atom(FALSE) 3 #SSCN: [1, 9999, 12]; I_TEST1(T1)
--SEL3 EXPLAIN SELECT C2 FROM T1; 1 #NSET2: [1, 9999, 12] 2 #PRJT2: [1, 9999, 12]; exp_num(2), is_atom(FALSE) 3 #CSCN2: [1, 9999, 12]; INDEX33555446(T1)

创建索引之后T1存在两个入口,CSCN T1基表和SSCN 二级索引I_TEST1。
在SEL2中,只要求获取C1,而二级索引上存在C1,且数据长度比基础表T1要短(一个C2的长度),因此查询走SSCN二级索引;
对于SEL3来说,二级索引上没有C2,因此要获取C2依然没有更好的入口,还是只能选择CSCN全表扫描。
##一般来说,我们认为CSCN和SSCN的耗时是差不多的,二者的区别在于,SSCN扫描出来的数据会按索引列排序,这一点在某些情况下是一大优势。

3.3 SSEK 二级索引范围扫描

--SEL4 EXPLAIN SELECT C1 FROM T1 WHERE C1 = 5; 1 #NSET2: [0, 249, 12] 2 #PRJT2: [0, 249, 12]; exp_num(2), is_atom(FALSE) 3 #SSEK2: [0, 249, 12]; scan_type(ASC), I_TEST1(T1), scan_range[5,5]

查询条件为“C1=XX”,且存在C1索引(I_TEST1),所以走的SSEK,需要注意的是操作符后面的描述scan_range[5,5],表示精准定位到5,多数情况下这样的查询是比较有效率的。

3.4 BLKUP 根据二级索引的ROWID回原表中取出全部数据

对SEL4中的查询需求做一点修改

--SEL5 EXPLAIN SELECT * FROM T1 WHERE C1 = 5; 1 #NSET2: [0, 249, 16] 2 #PRJT2: [0, 249, 16]; exp_num(3), is_atom(FALSE) 3 #BLKUP2: [0, 249, 16]; I_TEST1(T1) 4 #SSEK2: [0, 249, 16]; scan_type(ASC), I_TEST1(T1), scan_range[5,5]

执行计划中出现BLKUP操作符,由于索引I_TEST1上没有C2的数据,而查询需要查出整行数据(SELECT *),因此索引需要执行BLKUP回原表查找整行数据。

3.5 CSEK 聚簇索引范围扫描

聚簇索引是比较特殊的索引(对应操作符CSEK),在DM7上,同一张表只允许存在一个聚簇索引,默认建表时,基表就是一个ROWID的聚簇索引,所以对ROWID的精准定位会走CSEK

--SEL6 EXPLAIN SELECT C1 FROM T1 WHERE ROWID = 6; 1 #NSET2: [0, 1, 12] 2 #PRJT2: [0, 1, 12]; exp_num(2), is_atom(FALSE) 3 #CSEK2: [0, 1, 12]; scan_type(ASC), INDEX33555446(T1), scan_range[exp_cast(6),exp_cast(6)]

如果我们在基表上再创建了一个自定义聚簇索引

CREATE CLUSTER INDEX I_INDEX2 ON T1(C2);

那么ROWID这个聚簇索引就不存在了,取而代之的是按C2为顺序的聚簇索引

--SEL7 EXPLAIN SELECT C1 FROM T1 WHERE ROWID = 6; 1 #NSET2: [1, 249, 12] 2 #PRJT2: [1, 249, 12]; exp_num(1), is_atom(FALSE) 3 #SLCT2: [1, 249, 12]; T1.ROWID = var1 4 #SSCN: [1, 9999, 12]; I_TEST1(T1)
--SEL8 EXPLAIN SELECT C1 FROM T1 WHERE C2 = 6; 1 #NSET2: [0, 249, 8] 2 #PRJT2: [0, 249, 8]; exp_num(1), is_atom(FALSE) 3 #CSEK2: [0, 249, 8]; scan_type(ASC), I_INDEX2(T1), scan_range[6,6]

对比SEL6和SEL7,同样的查询语句,SEL6走的CSEK,而SEL7走的SSCN,这是因为在SEL7中,已经没有ROWID的聚簇索引了,而普通二级索引I_TEST1上正好有查询需要的C1以及ROWID,所以默认选择SSCN二级索引扫描I_TEST1。
同时我们可以看到,自定义C2的聚簇索引后,在SEL8中,对C2的精准过滤走的是CSEK,且不存在BLKUP。

单表的操作符大致就是这几种,这些是所有查询的数据来源,其他的复杂条件等都是在此基础上进行的操作。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服