在数据库的使用中,书写正确的SQL语句只是完成了万里长征的第一步。事实上,在DBA的日常的工作中,SQL优化占据了很大的一部分的内容,本文就将给大家介绍一些SQL优化有关的基础知识。
1.基本概念
在理解如何优化SQL语句之前,我们首先要了解几个基本概念
1.1执行计划
执行计划是SQL语句的执行方式,由查询优化器为语句设计的执行方式,交给执行器去执行。在SQL命令行使用EXPLAIN可以打印出语句的执行计划。
1.2操作符
操作符是SQL执行的基本单元,所有的SQL语句最终都是转换成一连串的操作符最后在服务器上执行,得到需要的结果,
操作符也是读懂执行计划的基础,这里列举一下经常出现的操作符
CSCN SSCN SSEK CSEK BLKUP
这些操作符是SQL查询数据的原始来源,SQL里面出现的基表,都会出现在这些操作符的描述中,通过这些操作符来确定对应的表在执行计划中在何时以什么样的方式进入。
下面列出操作符的具体含义
CSCN :基础全表扫描(a),从头到尾,全部扫描
SSCN :二级索引扫描(b), 从头到尾,全部扫描
SSEK :二级索引范围扫描(b) ,通过键值精准定位到范围或者单值
CSEK :聚簇索引范围扫描(c) ,通过键值精准定位到范围或者单值
BLKUP :根据二级索引的ROWID 回原表中取出全部数据(b + a)
更多的操作符解释,可以参见DM7系统管理员手册附录4《执行计划操作符》
2.搭建实验环境
创建表T1并录入数据,相关SQL语句如下
SQL> CREATE TABLE T1(C1 INT,C2 INT);
SQL> insert into t1 select level,level from dualconnect by level < 10000;
3.检验执行计划
--SEL1
SQL> 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
下面我们创建一条索引
SQL> create index i_test1 on t1(c1);
再看下面这个语句的计划
--SEL2
SQL> 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
SQL> 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,且数据长度比基础表要少(多出一个C2),索引选择SSCN
对于SEL3,依然没有更好的入口,还是选择CSCN全表
##一般来说,我们认为CSCN和SSCN的耗时是差不多了,SSCN和CSCN的区别在于,SSCN 扫描出来的数据是按索引列排序的,这一点在一些情况下可以利用
现在看SSEK的情况
--SEL4
SQL> 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]
查询条件C1 = 多少,存在C1索引,需要注意的是操作符后面的描述scan_range[5,5],表示精准定位到5,无疑,多数情况下这样是比较有效率的。
另外一点,SSEK 上面出现了BLKUP操作符,由于I_TEST1上没有C2的数据,而查询需要SELECT *,索引需要BLKUP回原表查找整行数据
很容易的,我们可以想到如果只查询C1,那么BLKUP操作符应该不存在,验证一下
--SEL5
SQL> 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]
确实如此
聚簇索引是比较特殊的索引(对应操作符CSEK),在DM7上,同一张表的聚簇索引只允许存在一个,默认建表时(不建堆表的情况下),基表就是一个ROWID聚簇索引,可以预见到对ROWID的精准定位应该会走CSEK
--SEL6
SQL> 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)]
如果我们创建了一个自定义聚簇索引
SQL> create cluster index i_index2 on t1(c2);
那么ROWID这个聚簇索引就不存在了,取而代指的是按C2为顺序的聚簇索引
--SEL7
SQL> 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)
##这里查询中需要C1以及ROWID,而普通二级索引I_TEST1上正好都有,且比聚簇索引的长度要短,所以选择SSCN I_TEST1
--SEL8
SQL> 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]
我们可以看到,对ROWID的精准定位不再走精准定位的CSEK,而是全索引扫描I_TEST1,对C2的精准过滤走的CSEK,且不存在BLKUP
上海腾科教育科技有限公司聚焦ICT人才培养和教育服务、培养数字化时代的新型ICT人才、促进良性的ICT人才生态发展,提供面授教育、在线教育、IT人才培养与就业、高校专业共建与实验室建设、企业人才定制培养,以及教育科技领域内的技术开发、咨询、服务以及解决方案,专注高校ICT课程课件研发、基于华为公有云的云实验实训平台建设,致力于ICT技术在高等院校的引进、开发和推广,是一家全国性的专业ICT行业技术认证服务公司。
腾科教育是华为授权的培训合作伙伴,专业提供华为ICT技术架构认证、平台与服务认证、行业服务认证等华为职业认证服务,连续多年荣获华为年度最佳合作伙伴贡献奖、华为优秀战略合作伙伴、上海HCIE精英俱乐部伙伴,连续多年举办腾科技术嘉年华华为专场,并协办华为ICT大赛、华为生态伙伴精英赛伙伴赛分赛场、华为人才双选会上海场等活动和赛事,提供面向华为生态合作伙伴和ICT学员的人才供需双选平台及ICT学员就业通道,培养ICT行业所需创新型、融合型人才。