在数据库的使用中,数据库的性能往往是至关重要的问题,而数据库的性能问题最终基本都要涉及到SQL优化,本文就将详细介绍一些达梦中SQL优化的执行计划解读。
无论是什么数据库,一般SQL优化我们都需要去查看SQL的执行计划,了解SQL具体是慢在哪里,才知道从哪里开始优化。
那么什么是执行计划呢?
执行计划是SQL语句的执行方式,由查询优化器为语句设计的执行方式,交给执行器去执行。在达梦中我们可以在SQL命令行使用EXPLAIN可以打印出语句的执行计划。
例如下面就是一个最基本的执行计划:
SQL> explain select * from SYSOBJECTS;
1 #NSET2: [0, 1531, 396]
2 #PRJT2: [0, 1531, 396]; exp_num(17), is_atom(FALSE)
3 #CSCN2: [0, 1531, 396]; SYSINDEXSYSOBJECTS(SYSOBJECTS as SYSOBJECTS)
从上面的执行计划中我们可以看到哪些信息呢?
达梦中执行计划涉及到的一些主要操作符有:
接下来我们结合实例来介绍下这些操作符:
–准备测试表和数据:
DROP TABLE T1;
DROP TABLE T2;
CREATE TABLE T1(C1 INT ,C2 CHAR(1),C3 VARCHAR(10) ,C4 VARCHAR(10) );
CREATE TABLE T2(C1 INT ,C2 CHAR(1),C3 VARCHAR(10) ,C4 VARCHAR(10) );
INSERT INTO T1
SELECT LEVEL C1,CHR(65+MOD(LEVEL,57)) C2,'TEST',NULL FROM DUAL CONNECT BY LEVEL<=10000;
INSERT INTO T2
SELECT LEVEL C1,CHR(65+MOD(LEVEL,57)) C2,'TEST',NULL FROM DUAL CONNECT BY LEVEL<=10000;
CREATE INDEX IDX_C1_T1 ON T1(C1);
SP_INDEX_STAT_INIT(USER,'IDX_C1_T1');
–NSET:收集结果集
说明:用于结果集收集的操作符, 一般是查询计划的顶层节点。
SQL> EXPLAIN SELECT * FROM T1;
1 #NSET2: [1, 10000, 156]
2 #PRJT2: [1, 10000, 156]; exp_num(5), is_atom(FALSE)
3 #CSCN2: [1, 10000, 156]; INDEX33555571(T1)
–PRJT:投影
说明:关系的“投影”(project)运算,用于选择表达式项的计算;广泛用于查询,排序,函数索引创建等。
SQL> EXPLAIN SELECT * FROM T1;
1 #NSET2: [1, 10000, 156]
2 #PRJT2: [1, 10000, 156]; exp_num(5), is_atom(FALSE)
3 #CSCN2: [1, 10000, 156]; INDEX33555571(T1)
–SLCT:选择
说明:关系的“选择” 运算,用于查询条件的过滤。
SQL> EXPLAIN SELECT * FROM T1 WHERE C2='TEST';
1 #NSET2: [1, 250, 156]
2 #PRJT2: [1, 250, 156]; exp_num(5), is_atom(FALSE)
3 #SLCT2: [1, 250, 156]; T1.C2 = 'TEST'
4 #CSCN2: [1, 10000, 156]; INDEX33555571(T1)
–AAGR:简单聚集
说明:用于没有group by的count,sum,age,max,min等聚集函数的计算。
SQL> EXPLAIN SELECT COUNT(*) FROM T1 WHERE C1 = 10;
1 #NSET2: [0, 1, 4]
2 #PRJT2: [0, 1, 4]; exp_num(1), is_atom(FALSE)
3 #AAGR2: [0, 1, 4]; grp_num(0), sfun_num(1)
4 #SSEK2: [0, 1, 4]; scan_type(ASC), IDX_C1_T1(T1), scan_range[10,10]
–FAGR:快速聚集
说明:用于没有过滤条件时从表或 索引快速获取 MAX/MIN/COUNT值,DM数据库是世界上单表不带过滤条件下取COUNT值最快的数据库。
SQL> EXPLAIN SELECT COUNT(*) FROM T1;
1 #NSET2: [1, 1, 0]
2 #PRJT2: [1, 1, 0]; exp_num(1), is_atom(FALSE)
3 #FAGR2: [1, 1, 0]; sfun_num(1),
SQL> EXPLAIN SELECT MAX(C1) FROM T1;
1 #NSET2: [1, 1, 4]
2 #PRJT2: [1, 1, 4]; exp_num(1), is_atom(FALSE)
3 #FAGR2: [1, 1, 4]; sfun_num(1), IDX_C1_T1
–HAGR:HASH分组聚集
说明:用于分组列没有索引只能走全表扫描的分组聚集,C2列没有创建索引。
SQL> EXPLAIN SELECT COUNT(*) FROM T1 GROUP BY C2;
1 #NSET2: [2, 100, 48]
2 #PRJT2: [2, 100, 48]; exp_num(1), is_atom(FALSE)
3 #HAGR2: [2, 100, 48]; grp_num(1), sfun_num(1);
4 #CSCN2: [1, 10000, 48]; INDEX33555571(T1)
–SAGR:流分组聚集
说明:用于分组列是有序的情况下可以使用流分组聚集,C1上已经创建了索引,SAGR2性能优于HAGR2。
SQL> EXPLAIN SELECT COUNT(*) FROM T1 GROUP BY C1;
1 #NSET2: [2, 100, 4]
2 #PRJT2: [2, 100, 4]; exp_num(1), is_atom(FALSE)
3 #SAGR2: [2, 100, 4]; grp_num(1), sfun_num(1)
4 #SSCN: [1, 10000, 4]; IDX_C1_T1(T1)
–BLKUP:二次扫描
说明:先使用2级别索引定位,再根据表的主键、聚集索引、 rowid等信息定位数据行。
SQL> EXPLAIN SELECT * FROM T1 WHERE C1=10;
1 #NSET2: [0, 1, 156]
2 #PRJT2: [0, 1, 156]; exp_num(5), is_atom(FALSE)
3 #BLKUP2: [0, 1, 156]; IDX_C1_T1(T1)
4 #SSEK2: [0, 1, 156]; scan_type(ASC), IDX_C1_T1(T1), scan_range[10,10]
–CSCN:全表扫描
说明:CSCN2是CLUSTER INDEX SCAN的缩写即通过聚集索引扫描全表,全表扫描是最简单的查询,如果没有选择谓词,或者没有索引可以利用,则系统一般只能做全表扫描。在一个高并发的系统中应尽量避免全表扫描。
SQL> EXPLAIN SELECT * FROM T1;
1 #NSET2: [1, 10000, 156]
2 #PRJT2: [1, 10000, 156]; exp_num(5), is_atom(FALSE)
3 #CSCN2: [1, 10000, 156]; INDEX33555571(T1)
–SSEK、CSEK、SSCN:索引扫描
说明:
–SSEK
SQL> EXPLAIN SELECT * FROM T1 WHERE C1=10;
1 #NSET2: [0, 1, 156]
2 #PRJT2: [0, 1, 156]; exp_num(5), is_atom(FALSE)
3 #BLKUP2: [0, 1, 156]; IDX_C1_T1(T1)
4 #SSEK2: [0, 1, 156]; scan_type(ASC), IDX_C1_T1(T1), scan_range[10,10]
–CSEK
SQL> CREATE CLUSTER INDEX IDX_C1_T2 ON T2(C1);
SQL> EXPLAIN SELECT * FROM T2 WHERE C1=10;
1 #NSET2: [0, 250, 156]
2 #PRJT2: [0, 250, 156]; exp_num(5), is_atom(FALSE)
3 #CSEK2: [0, 250, 156]; scan_type(ASC), IDX_C1_T2(T2), scan_range[10,10]
–SSCN
SQL> CREATE INDEX IDX_C1_C2_T1 ON T1(C1,C2);
SQL> EXPLAIN SELECT C1,C2 FROM T1;
1 #NSET2: [1, 10000, 60]
2 #PRJT2: [1, 10000, 60]; exp_num(3), is_atom(FALSE)
3 #SSCN: [1, 10000, 60]; IDX_C1_C2_T1(T1)
至此,主要的执行计划操作符就介绍的差不多了。
3.1、嵌套循环连接(NEST LOOP)
原理:
两层嵌套循环结构,有驱动表和被驱动表之分。 选定一张表作为驱动表,遍历驱动表中的每一行,根据连接条件去匹配第二 张表中的行。驱动表的行数就是循环的次数,这个很大程度影响了执行效率。
需注意的问题:
选择小表作为驱动表。统计信息尽量准确,保证优化器选对驱动表。
大量的随机读。如果没有索引,随机读很致命,每次循环只能读一块, 不能读多块。使用索引可以解决这个问题。
使用场景:
例子:
过滤列和连接列都没有索引,也可以走nest loop,但是该计划很差。如下面的计划代价就很大。
SQL> explain select /*+use_nl(t1,t2)*/*
from t1 inner join t2
on t1.c1=t2.c1 where t1.c2='A';
1 #NSET2: [17862, 24950, 296]
2 #PRJT2: [17862, 24950, 296]; exp_num(8), is_atom(FALSE)
3 #SLCT2: [17862, 24950, 296]; T1.C1 = T2.C1
4 #NEST LOOP INNER JOIN2: [17862, 24950, 296];
5 #SLCT2: [1, 250, 148]; T1.C2 = 'A'
6 #CSCN2: [1, 10000, 148]; INDEX33555571(T1)
7 #CSCN2: [1, 10000, 148]; IDX_C1_T2(T2)
我们可以加上索引来进行优化:
create index idx_t1_c2 on t1(c2);
create index idx_t2_c1 on t2(c1); dbms_stats.gather_index_stats(user,'IDX_ T1_C2'); dbms_stats.gather_index_stats(user,'IDX_ T2_C1');
优化后执行计划:
SQL> explain select /*+use_nl(t1,t2)*/*
from t1 inner join t2
on t1.c1=t2.c1 where t1.c2='A';
1 #NSET2: [17821, 24950, 296]
2 #PRJT2: [17821, 24950, 296]; exp_num(8), is_atom(FALSE)
3 #SLCT2: [17821, 24950, 296]; T1.C1 = T2.C1
4 #NEST LOOP INNER JOIN2: [17821, 24950, 296];
5 #BLKUP2: [0, 250, 148]; IDX_T1_C2(T1)
6 #SSEK2: [0, 250, 148]; scan_type(ASC), IDX_T1_C2(T1), scan_range['A','A']
7 #CSCN2: [1, 10000, 148]; IDX_C1_T2(T2)
3.2、哈希连接(hash join)
原理:
使用较小的Row source 作为Hash table和Bitmap, 而第二个row source被hashed,根据bitmap与第一个row source生成的hash table 相匹配,bitmap查找的速度极快。
hash join特点:
由于hash连接比较消耗内存,如果系统有很多这种连接时,需调整以下3个参数:
例子:
SQL> explain select *
from t1 inner join t2
on t1.c1=t2.c1 where t1.c2='A';
1 #NSET2: [1, 24950, 296]
2 #PRJT2: [1, 24950, 296]; exp_num(8), is_atom(FALSE)
3 #HASH2 INNER JOIN: [1, 24950, 296]; KEY_NUM(1);
4 #NEST LOOP INDEX JOIN2: [1, 24950, 296]
5 #ACTRL: [1, 24950, 296];
6 #BLKUP2: [0, 250, 148]; IDX_T1_C2(T1)
7 #SSEK2: [0, 250, 148]; scan_type(ASC), IDX_T1_C2(T1), scan_range['A','A']
8 #CSEK2: [1, 2, 0]; scan_type(ASC), IDX_C1_T2(T2), scan_range[T1.C1,T1.C1]
9 #CSCN2: [1, 10000, 148]; IDX_C1_T2(T2)
需要注意:如果不是等值连接则会走nest loop连接。
SQL> explain select *
from t1 inner join t2
on t1.c1 > t2.c1 where t1.c2='A';
1 #NSET2: [2, 125000, 296]
2 #PRJT2: [2, 125000, 296]; exp_num(8), is_atom(FALSE)
3 #NEST LOOP INDEX JOIN2: [2, 125000, 296]
4 #BLKUP2: [0, 250, 148]; IDX_T1_C2(T1)
5 #SSEK2: [0, 250, 148]; scan_type(ASC), IDX_T1_C2(T1), scan_range['A','A']
6 #CSEK2: [2, 375, 0]; scan_type(ASC), IDX_C1_T2(T2), scan_range(null2,T1.C1)
3、排序合并连接(MERGE SORT)
特点:
应用场景:
通常情况下,merge sort join需要消耗大量的cpu和内存,效率都不会太高。如果存在相关索引可以消除sort,那么CBO可能会考虑该连接方式。
例子:
SQL> explain select /*+use_merge(t1 t2)*/ t1.c1,t2.c1
from t1 inner join t2 on t1.c1=t2.c1 where t2.c2='b';
1 #NSET2: [4, 24950, 56]
2 #PRJT2: [4, 24950, 56]; exp_num(2), is_atom(FALSE)
3 #SLCT2: [4, 24950, 56]; T2.C2 = 'b'
4 #MERGE INNER JOIN3: [4, 24950, 56];
5 #SSCN: [1, 10000, 4]; IDX_C1_T1(T1)
6 #CSCN2: [1, 10000, 52]; IDX_C1_T2(T2)
4.1、什么是查询转换?
查询转换是优化器自动做的,在生成执行计划之前,等价改写 查询语句的形式,以便提升效率和产生更好的执行计划。它决 定是否重写用户的查询,常见的转换有谓词传递、视图拆分、 谓词推进、关联/非关联子查询改写等。
了解优化器查询转换的特性,会帮助我们更好的看懂执行计划, 也会对我们优化sql起到指导的作用。优化器的查询转换有很 多限制条件,我们可以根据类似的原理举一反三,进行手工的 sql改写,从到得到更好的执行计划。
4.2、谓词转换
什么是谓词转换呢?大致就是指我们可以根据A=B,B=C,可以推导出A=C的形式。如下面的SQL:
select * from t1 inner join t2
on t1.c2=t2.c2 where t1.c1=100
and t2.c1=t1.c1
CBO经过谓词转换后,实际执行的语句其实是:
select * from t1 inner join t2
on t1.c2=t2.c2 where t1.c1=100
and t2.c1=t1.c1
and t2.c1=100 –-谓词传递
4.3、视图拆分
我们先创建一个视图:
SQL> create or replace view v_t1 as select t1.c1+t2.c1 as c11,
t2.c2,t1.c1 from t1,t2
2 3 where t1.c2=t2.c2;
操作已执行
我们看看视图定义里面SQL的执行计划:
SQL> explain select t1.c1+t2.c1 as c11,
t2.c2,t1.c1 from t1,t2
where t1.c2=t2.c2;
1 #NSET2: [5, 980099, 104]
2 #PRJT2: [5, 980099, 104]; exp_num(3), is_atom(FALSE)
3 #HASH2 INNER JOIN: [5, 980099, 104]; KEY_NUM(1);
4 #SSCN: [1, 10000, 52]; IDX_C1_C2_T1(T1)
5 #CSCN2: [1, 10000, 52]; INDEX33555575(T2)
而我们查询使用到视图时:
SQL> explain select a.c11,b.c2 from v_t1 a,t1 b where a.c1=b.c1 and a.c1=100;
1 #NSET2: [5, 98, 156]
2 #PRJT2: [5, 98, 156]; exp_num(2), is_atom(FALSE)
3 #NEST LOOP INNER JOIN2: [5, 98, 156];
4 #SSEK2: [0, 1, 52]; scan_type(ASC), IDX_C1_C2_T1(T1 as B), scan_range[(100,min),(100,max))
5 #PRJT2: [2, 98, 104]; exp_num(1), is_atom(FALSE)
6 #HASH2 INNER JOIN: [2, 98, 104]; KEY_NUM(1);
7 #SSEK2: [0, 1, 52]; scan_type(ASC), IDX_C1_C2_T1(T1), scan_range[(100,min),(100,max))
8 #CSCN2: [1, 10000, 52]; INDEX33555575(T2)
观察上面sql的执行计划,发现视图部分的子计划已经没有了。说明优化器进行等价改写,将视图的查询拆散了,和其他部分作为一个整体来生 成计划。视图拆分有很多限制,如果视图查询中含有distinc、union、 group by等操作,优化器就无法进行视图拆分。
Sql中使用过多的视图,会使sql变得复杂,优化器也难以生成最佳的执行计划,不能过度依赖优化器进行视图拆分。开发时应尽量减少视图的使用。
4.4、谓词推进
我们先看看下面这样一个SQL,可以看到子查询x相当于一个内联视图。
SQL> explain select * from
(select c1,c2 from t1 where c2='C') x where c1=100;
1 #NSET2: [0, 1, 60]
2 #PRJT2: [0, 1, 60]; exp_num(3), is_atom(FALSE)
3 #PRJT2: [0, 1, 60]; exp_num(3), is_atom(FALSE)
4 #SSEK2: [0, 1, 60]; scan_type(ASC), IDX_C1_C2_T1(T1), scan_range[(100,'C'),(100,'C')]
观察上面的执行计划,由于C2字段无索引,子查询X部分本应该走全表扫描, 但是计划中却走了C1字段的索引。说明优化器对原始sql做了如下的等价改写,将条件c1=100推到子查询X中:
–查询转换
select * from
(select c1,c2 from t1 where c2='C' and c1=100) x;
4.5、查询转换例子
–非关联子查询的转换:
SQL> explain select * from t1
where c1 in (select c1 from t2 ) and c2='A';
1 #NSET2: [1, 250, 156]
2 #PRJT2: [1, 250, 156]; exp_num(5), is_atom(FALSE)
3 #INDEX JOIN SEMI JOIN2: [1, 250, 156];
4 #SLCT2: [1, 250, 156]; T1.C2 = 'A'
5 #CSCN2: [1, 10000, 156]; INDEX33555571(T1)
6 #SSEK2: [1, 2, 0]; scan_type(ASC), IDX_T2_C1(T2), scan_range[T1.C1,T1.C1]
观察原始sql,T2的子查询是个非关联的子查询,完全可以把它生成一个独立的子计划。但是计划中TI和T2做了关联,说明优化器进行了如下的等价改写:
select * from t1
where exists (select 1 from t2 where t1.c1=t2.c1) and c2='A';
相关INI参数: REFED_EXISTS_OPT_FLAG,影响in和exists子查询的转换。
–外连接转换:
SQL> explain select t1.c1,t2.c2 from t1 left join t2
on t1.c1=t2.c1 where t2.c1=100 and t1.c2='A';
1 #NSET2: [0, 250, 104]
2 #PRJT2: [0, 250, 104]; exp_num(2), is_atom(FALSE)
3 #NEST LOOP INNER JOIN2: [0, 250, 104];
4 #SSEK2: [0, 1, 52]; scan_type(ASC), IDX_C1_C2_T1(T1), scan_range[(100,'A'),(100,'A')]
5 #BLKUP2: [0, 250, 52]; IDX_T2_C1(T2)
6 #SSEK2: [0, 250, 52]; scan_type(ASC), IDX_T2_C1(T2), scan_range[100,100]
观察上面的计划发现,原始sql是外连接,计划中却变成了内连接。这是优化器根 据sql语义判断,就是等价于下面的内连接:
select t1.c1,t2.c2
from t1 inner join t2 on t1.c1=t2.c1 where t2.c1=100 and t1.c2='A';
达梦数据库的优化器同oracle、mysql一样,都是计算出各种方案的代价,并选择一个代价最低的执行方式,去执行sql。
如上所述,在oracle、mysql中常用的sql优化经验,在达梦数据库也是基本适用的,包括常用的联合索引过滤条件,根据过滤性选择放在最左侧,遇到等于、大于、小于时,等于号的条件放在最左侧,注意查询时,where条件里的条件值,要与字段的数据类型对应,以防发生隐式转换,导致优化器计算后代价过高不走索引,比如 id = '10' ,因为id字段的类型为number,加了单引号的10,会被数据库认为是字符串,就会发生隐式转换,因此优化器可能会不走索引。
达梦数据库针对 like '%……%' 的查询,提供了参数LIKE_OPT_FLAG,可以参考此参数给出的方案,进行sql优化。
使用disql时,执行计划的内容有所不同,但其实跟manager返回的是一样的,可以查看“社区文档:DM 执行计划解读 | 达梦技术文档”
关于SQL优化主要还是需要先分析系统当前哪些语句是性能影响最大的,一般是那些单个SQL执行慢且执行频率高的。
然后再结合执行计划去进行优化,优化大致思路为:
使用索引:选择合适的索引。
改写SQL:
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
应改为:
select id from t where name like 'abc%'
9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,
否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
11.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
12.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
13.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,
如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
14.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,
因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
15.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
16.尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,
其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
17.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
18.避免频繁创建和删除临时表,以减少系统表资源的消耗。
19.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
20.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,
以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
21.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
22.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
23.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
24.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
25.尽量避免大事务操作,提高系统并发能力。
26.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
1. PRJT2
关系的“投影”(project)运算,用于选择表达式项的计算;广泛用于查询,排序,函数索引创建等。
SQL>explain select c1 + c2 from t1;
explain select c1 + c2 from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
2. NSET2
结果集(result set)收集,一般是查询计划的顶层节点。
SQL>explain select c1 + c2 from t1;
explain select c1 + c2 from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
3. SLCT2
关系的“选择”(select)运算,用于查询条件的过滤。
explain select * from t1 where c1 >1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#SLCT2: [6, 1, 0]; T1.C1 > 1
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
4. NEST LOOP INNER JOIN2
简写为NLI2,嵌套循环内连接;没有索引可用,且无法用HASH,(如不等值比较),则可能使用NLI2
explain select * from t1, t2 where t1.c1 >t2.c2;
#NSET2: [19, 1, 0]
#PRJT2: [19, 1, 0]; exp_num(4), is_atom(FALSE)
#SLCT2: [19, 1, 0]; T1.C1 > T2.C2
#NEST LOOP INNER JOIN2: [19, 1, 0];
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
5. MERGE INNER JOIN3
简写为MI3,归并内连接;有索引可用时,有可能使用MI3。
explain select a.c1, b.c1 from tx a, tx b where a.c1 = b.c1;
#NSET2: [29, 100000, 0]
#PRJT2: [29, 100000, 0]; exp_num(2), is_atom(FALSE)
#MERGE INNER JOIN3: [29, 100000, 0];
#CSCN2: [23, 100000, 0]; INDEX33555463(TX)
#CSCN2: [23, 100000, 0]; INDEX33555463(TX)
6. NEST LOOP FULL JOIN2
简写为NLFO2,嵌套循环全外连接。一般不等值连接时使用。
explain select *from t1 full join t2 on t1.c1 <> t2.c1;
#NSET2: [19, 1, 0]
#PRJT2: [19, 1, 0]; exp_num(4), is_atom(FALSE)
#NEST LOOP FULL JOIN2: [19, 1, 0]; join condition(T1.C1 <> T2.C1)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
7. NEST LOOP LEFT JOIN2
简写为NLLO2,嵌套循环左外连接。一般不等值连接时使用。
explain select *from t1 left join t2 on t1.c1 <> t2.c1;
#NSET2: [19, 1, 0]
#PRJT2: [19, 1, 0]; exp_num(4), is_atom(FALSE)
#NEST LOOP LEFT JOIN2: [19, 1, 0]; join condition(T1.C1 <> T2.C1)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
8. HASH LEFT JOIN2
简写为HLO2,HASH左外连接。一般等值连接时使用。
explain select *from t1 left join t2 on t1.c1 = t2.c1;
#NSET2: [13, 1, 0]
#PRJT2: [13, 1, 0]; exp_num(4), is_atom(FALSE)
#HASH LEFT JOIN2: [13, 1, 0]; key_num(1),
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
9. HASH RIGHT JOIN2
简写为HRO2,HASH右外连接。下面的例子,做一个tx与t2的左连接。因为t2的行数很小,tx比较大,所以自动转为右连接,在t2上建hash的代价比较小。
explain select * from tx left join t2 on t2.c1 = tx.c1 + 2;
#NSET2: [39, 100000, 0]
#PRJT2: [39, 100000, 0]; exp_num(4), is_atom(FALSE)
#HASH RIGHT JOIN2: [39, 100000, 0]; key_num(1),
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
#CSCN2: [23, 100000, 0]; INDEX33555463(TX)
10. HASH FULL JOIN2
简写为HFO2,HASH全外连接。等值连接时可用;
explain select *from t1 full join t2 on t1.c1 = t2.c1;
#NSET2: [13, 1, 0]
#PRJT2: [13, 1, 0]; exp_num(4), is_atom(FALSE)
#HASH FULL JOIN2: [13, 1, 0]; key_num(1),
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
11. TOPN2
取前N个记录;
explain select top 10 * from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#TOPN2: [6, 1, 0]; top_num(10)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
12. UNION ALL
Union all 运算,
explain select *from t1 union all select *from t2;
#NSET2: [13, 2, 0]
#PRJT2: [13, 2, 0]; exp_num(2), is_atom(FALSE)
#UNION ALL: [13, 2, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
13. UNION
集合的并运算,
explain select *from t1 union select *from t2;
#NSET2: [13, 2, 0]
#PRJT2: [13, 2, 0]; exp_num(2), is_atom(FALSE)
#UNION: [13, 2, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555457(T1)
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555458(T2)
14. EXCEPT / EXCEPT ALL
集合的差运算
explain select * from t1 except select * from t2;
#NSET2: [13, 1, 0]
#PRJT2: [13, 1, 0]; exp_num(2), is_atom(FALSE)
#EXCEPT: [13, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555476(T2)
15. INTER SECT/ INTERSECT ALL
集合的交运算
explain select * from t1 intersect select * from t2;
#NSET2: [13, 1, 0]
#PRJT2: [13, 1, 0]; exp_num(2), is_atom(FALSE)
#INTERSECT: [13, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555476(T2)
16. INSERET/VINS2
插入记录,其中VINS2用于列存储表
explain insert into t1 values(1, 1);
#INSERT : [0, 0, 0]; table(T1), type(values)
17. HAGR2
Hash aggregate; HASH 分组,并计算聚集函数
explain select count(*) from tx group by c2;
#NSET2: [23, 1000, 0]
#PRJT2: [23, 1000, 0]; exp_num(1), is_atom(FALSE)
#HAGR2: [23, 1000, 0]; grp_num(1), sfun_num(1)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
18. SAGR2
Stream Aggregate;如果输入流是有序的,则使用流分组,并计算聚集函数
explain select count(*) from tx group by c1;
#NSET2: [23, 1000, 0]
#PRJT2: [23, 1000, 0]; exp_num(1), is_atom(FALSE)
#SAGR2: [23, 1000, 0]; grp_num(1), sfun_num(1)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
19. FAGR2
Fast aggregate;如果没有where条件,且取count(*), 或者基于索引的MAX/MIN值,则可以快速取得集函数的值
explain select count(*) from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(1), is_atom(FALSE)
#FAGR2: [6, 1, 0]; sfun_num(1)
20. AAGR2
简单聚集;如果没有分组(group by), 则总的就一个组,直接计算聚集函数
explain select count(*) from tx where c2 = 10;
#NSET2: [22, 1, 0]
#PRJT2: [22, 1, 0]; exp_num(1), is_atom(FALSE)
#AAGR2: [22, 1, 0]; grp_num(0), sfun_num(1)
#SLCT2: [22, 2500, 0]; exp_cast(TX.C2) = var1
#CSCN2: [22, 100000, 0]; INDEX33555479(TX)
21. HASH LEFT SEMI JOIN2
HASH 左半连接; 扫描左表建立hash表,扫描右表探测HASH表,最后输出被探测到的左表的行
explain select * from t1 where c1 in (select c2 from tx);
#NSET2: [35, 1, 0]
#PRJT2: [35, 1, 0]; exp_num(3), is_atom(FALSE)
#HASH LEFT SEMI JOIN2: [35, 1, 0];
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#PRJT2: [22, 100000, 0]; exp_num(1), is_atom(FALSE)
#CSCN2: [22, 100000, 0]; INDEX33555479(TX)
22. CSCN2/VSCN2
聚集索引扫描(cluster index scan); VSCN2用于列存储表
explain select *from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
23. DSCN
Dynamic table scan; 动态视图扫描
explain select * from v$threads;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#DSCN: [6, 1, 0]; SYSINDEXV$THREADS(V$THREADS)
24. DELETE/VDEL2
删除数据, 其中VDEL2用于列存储表
explain delete from t1 where c1 = 1;
#DELETE : [0, 0, 0]; table(T1), type(select)
#TEMP TABLE SPOOL: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(1), is_atom(FALSE)
#SLCT2: [6, 1, 0]; T1.C1 = 1
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
25. SORT3
排序
explain select * from t1 order by c1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#SORT3: [6, 1, 0]; key_num(1), is_distinct(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
26. TEMP TABLE SPOOL
临时表,临时存放数据;如delete/update时,临时存放ROWID, PK等定位信息
explain delete from t1 where c1 = 1;
#DELETE : [0, 0, 0]; table(T1), type(select)
#TEMP TABLE SPOOL: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(1), is_atom(FALSE)
#SLCT2: [6, 1, 0]; T1.C1 = 1
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
27. PIPE2
管道;先做一遍右儿子,然后执行左儿子,并把左儿子的数据向上送,直到左儿子不再有数据。
explain select (select 2) from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#PIPE2: [6, 1, 0]
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#SPOOL2: [0, 1, 0]; key_num(1), spool_num(0)
#PRJT2: [0, 1, 0]; exp_num(1), is_atom(TRUE)
#CSCN2: [0, 1, 0]; SYSINDEXSYSDUAL(SYSDUAL)
28. SPOOL2
也是临时表;和TEMP TABLE SPOOL(NTTS)不同的是,它的数据集不向父亲节点传送,而是被编号,用编号和KEY来定位访问;而TEMP TABLE SPOOL的数据,主动传递给父亲节点;
explain select (select 2) from t1;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(2), is_atom(FALSE)
#PIPE2: [6, 1, 0]
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#SPOOL2: [0, 1, 0]; key_num(1), spool_num(0)
#PRJT2: [0, 1, 0]; exp_num(1), is_atom(TRUE)
#CSCN2: [0, 1, 0]; SYSINDEXSYSDUAL(SYSDUAL)
29. CSEK2
聚集索引数据定位;(cluster index seek)
explain select *from tx where c1 = 20;
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#CSEK2: [6, 1, 0]; scan_type(UNIQUE),INDEX33555479(TX), scan_range[20,20]
30. SSEK2
2级索引数据定位;(second index seek)
create index ind2 on tx(c2);
explain select c2 from tx where c2 = 'aaa';
#NSET2: [6, 2500, 0]
#PRJT2: [6, 2500, 0]; exp_num(1), is_atom(FALSE)
#SSEK2: [6, 2500, 0]; scan_type(ASC), IND2(TX), scan_range[aaa,aaa]
31. BLKUP2
(Batch lookup), 使用2级别索引的定位信息, 在聚集索引中查找数据
explain select * from tx where c2 = 'aaa';
#NSET2: [20, 2500, 0]
#PRJT2: [20, 2500, 0]; exp_num(4), is_atom(FALSE)
#BLKUP2: [20, 2500, 0]; IND2(TX)
#SSEK2: [20, 2500, 0]; scan_type(ASC), IND2(TX), scan_range[aaa,aaa]
32. NEST LOOP SEMI JOIN2
嵌套循环半连接。通常用于无法使用HASH, 索引的不等值的In/Exists;效率比较差。
explain select * from t1 where exists (select * from t2 where t1.c1 <> t2.c1);
#NSET2: [19, 1, 0]
#PRJT2: [19, 1, 0]; exp_num(3), is_atom(FALSE)
#NEST LOOP SEMI JOIN2: [19, 1, 0]; join condition(T1.C1 <> T2.C1)
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSCN2: [6, 1, 0]; INDEX33555476(T2)
33. NEST LOOP INDEX JOIN2
嵌套循环索引连接(IJI2)。 右表上有索引, 对于左表的每一行,利用索引在右表中定位。这是DM6中最常用的优化方式;DM7由于HASH连接比较成熟,因此其重要性略有下降。
explain select *from t1, tx where t1.c1 = tx.c1;
#NSET2: [26, 1, 0]
#PRJT2: [26, 1, 0]; exp_num(5), is_atom(FALSE)
#NEST LOOP INDEX JOIN2: [26, 1, 0]
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSEK2: [19, 1, 0]; scan_type(UNIQUE),INDEX33555479(TX), scan_range[T1.C1,T1.C1]
34. HASH2 INNER JOIN
HASH内连接(HI3);无法利用索引时,系统一般采用HASH连接。
explain select *from t1, t2 where t1.c1 = t2.c1;
#NSET2: [13, 1, 0]
#PRJT2: [13, 1, 0]; exp_num(4), is_atom(FALSE)
#HASH2 INNER JOIN: [13, 1, 0]; KEY_NUM(1);
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSCN2: [6, 1, 0]; INDEX33555476(T2)
35. INDEX JOIN SEMI JOIN2
索引半连接。在半连接的右表中,有索引可利用时使用,如下例tx(c1)上有索引。
explain select *from t1 where exists (select * from tx where t1.c1 = tx.c1);
#NSET2: [8, 1, 0]
#PRJT2: [8, 1, 0]; exp_num(3), is_atom(FALSE)
#INDEX JOIN SEMI JOIN2: [8, 1, 0];
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSEK2: [19, 1, 0]; scan_type(UNIQUE), INDEX33555479(TX), scan_range[T1.C1,T1.C1]
36. HASH RIGHT SEMI JOIN2
HASH右半连接(HRS2)。半连接一般情况下,总是在左表建立HASH, 如果右表数据更少,则转化为右半连接。
explain select * from tx where exists (select *from t1 where tx.c3 = t1.c2);
#NSET2: [40, 1, 0]
#PRJT2: [40, 1, 0]; exp_num(4), is_atom(FALSE)
#HASH RIGHT SEMI JOIN2: [40, 1, 0];
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
37. HASH RIGHT SEMI JOIN32
HASH右半连接。这个专门用来处理all/any/some等谓词。但all/any/some不是必须用这个操作符。
explain select *from tx where c3 > all(select c2 from t1);
#NSET2: [30, 100000, 0]
#PRJT2: [30, 100000, 0]; exp_num(4), is_atom(FALSE)
#HASH RIGHT SEMI JOIN32: [30, 100000, 0]; op all;, join condition(TX.C3 > DMTEMPVIEW_00001021.col_name)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
#PRJT2: [6, 1, 0]; exp_num(1), is_atom(FALSE)
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
38. MERGE SEMI JOIN3
归并半连接。如果连接的关键字正好是索引列,则可以使用。
explain select * from tx a where exists (select *from tx b where a.c1 = b.c1 and b.c3 = 10);
#NSET2: [27, 2500, 0]
#PRJT2: [27, 2500, 0]; exp_num(4), is_atom(FALSE)
#MERGE SEMI JOIN3: [27, 2500, 0];
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
#SLCT2: [23, 2500, 0]; TX.C3 = 10
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
39. CONST VALUE LIST
系统自动创建的一个常量列表,用于与实体表做连接。
explain select *from t1 where c1 in (1, 2, 3);
#NSET2: [6, 1, 0]
#PRJT2: [6, 1, 0]; exp_num(3), is_atom(FALSE)
#HASH2 INNER JOIN: [6, 1, 0]; KEY_NUM(1);
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CONST VALUE LIST: [0, 3, 0]; row_num(3), col_num(1)
40. HEAP TABLE/HEAP TABLE SCAN
有时,系统会把重复利用的中间保存在一个临时表中,这个表称为HEAP TABLE;与之配套的是,一个专门的扫描操作符号。(HTAB/HSCN)
explain with v1 as
(select count(*) from tx)
select *
from v1 a, v1 b;
#NSET2: [89, 1, 0]
#PIPE2: [89, 1, 0]
#PRJT2: [66, 1, 0]; exp_num(2), is_atom(FALSE)
#NEST LOOP INNER JOIN2: [66, 1, 0];
#HEAP TABLE SCAN: [22, 1, 0]; table_no(0),
#HEAP TABLE SCAN: [22, 1, 0]; table_no(0),
#HEAP TABLE: [22, 1, 0]; table_no(0),
#PRJT2: [22, 1, 0]; exp_num(1), is_atom(FALSE)
#FAGR2: [22, 1, 0]; sfun_num(1)
41. FBTR
Fill Btr, 填充B树。建索引的时候使用。一般不能用explain看,到, 但是建索引的时候,可以在V$SQL_NODE_HISTORY中查到 。
42. UFLT
(Filter for Update from),这是个用于实现UPDATE FROM语句(SQL SERVER兼容性)的操作符号。以rowid作为key,建hash表,如果下层数据过来没有hash匹配项则插入到hash表,
explain update t1 set t1.c1 = tx.c2 from tx where t1.c2 = tx.c1;
#UPDATE : [0, 0, 0]; table(T1), type(select)
#TEMP TABLE SPOOL: [26, 1, 0]
#UFLT: [0, 0, 0]; IS_TOP_1(TRUE)
#PRJT2: [26, 1, 0]; exp_num(2), is_atom(FALSE)
#NEST LOOP INDEX JOIN2: [26, 1, 0]
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSEK2: [19, 1, 0]; scan_type(UNIQUE), INDEX33555479(TX), scan_range[T1.C2,T1.C2]
43. HIERARCHICAL QUERY (CNNTB)
用于实现层次查询。
explain select * from tx connect by prior c1 = c2 start with c3 = 0;
#NSET2: [118773, 12500000, 0]
#PRJT2: [118773, 12500000, 0]; exp_num(3), is_atom(FALSE)
#HIERARCHICAL QUERY: [118773, 12500000, 0];
#PRJT2: [23, 2500, 0]; exp_num(4), is_atom(FALSE)
#SLCT2: [23, 2500, 0]; TX.C3 = 0
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
#PRJT2: [23, 2500, 0]; exp_num(4), is_atom(FALSE)
#SLCT2: [23, 2500, 0]; var1 = exp_cast(TX.C2)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
44. INDEX JOIN LEFT JOIN2
索引左外连接,使用右表的索引。
explain select * from t1 left join tx on t1.c1 = tx.c1;
#NSET2: [26, 1, 0]
#PRJT2: [26, 1, 0]; exp_num(5), is_atom(FALSE)
#INDEX JOIN LEFT JOIN2: [26, 1, 0]
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
#CSEK2: [19, 1, 0]; scan_type(UNIQUE), INDEX33555479(TX), scan_range[T1.C1,T1.C1]
45. MPP BROADCAST
Mpp模式下,消息广播到各站点,包含必要的聚集函数合并计算
46. MPP GATHER
Mpp模式下,消息收集到主站点
47. MPP DISTRIBUTE
Mpp模式下,消息各站点的相互重分发, 一般在连接前做
48. MPP SCATTER
Mpp模式下,主站点向各从站点广播消息
49. PARALLEL
水平分区表的并行查询
50. RNSK
(Row number stop key),实现ORACLE兼容的rownum;
explain select * from tx where rownum = 10;
#NSET2: [23, 100000, 0]
#PRJT2: [23, 100000, 0]; exp_num(4), is_atom(FALSE)
#RNSK: [23, 100000, 0]; rownum = exp_cast(10)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
51. CNTS
用于实现全文索引的CONTAINS
52. SSCN
(second index scan), 直接使用2级索引进行扫描;
explain select c2 from tx;
#NSET2: [22, 100000, 0]
#PRJT2: [22, 100000, 0]; exp_num(1), is_atom(FALSE)
#SSCN: [22, 100000, 0]; IND2(TX)
53. AFUN
分析函数计算。
explain
SELECT c1, SUM(c3) OVER (PARTITION BY c1) FROM tx;
#NSET2: [23, 100000, 0]
#PRJT2: [23, 100000, 0]; exp_num(2), is_atom(FALSE)
#AFUN: [23, 100000, 0]; afun_num(1)
#CSCN2: [23, 100000, 0]; INDEX33555479(TX)
54. PSCN/ASCN/ESCN
PSCN: 批量参数当作表来扫描
ASCN: 数组当作表来扫描
ESCN: 外部表扫描
55. DISTINCT
去重。
explain select distinct c1 from tx;
#NSET2: [22, 1000, 0]
#PRJT2: [22, 1000, 0]; exp_num(1), is_atom(FALSE)
#DISTINCT: [22, 1000, 0]
#SSCN: [22, 100000, 0]; IND2(TX)
56. HASH LEFT SEMI MUTIPLE JOIN
多列HASH左半连接, 用于实现多列IN。
explain select * from t1 where (c1, c2) not in (select c1, c2 from tx);
#NSET2: [35, 1, 0]
#PRJT2: [35, 1, 0]; exp_num(3), is_atom(FALSE)
#HASH LEFT SEMI MULTIPLE JOIN: [35, 1, 0]; (ANTI), join condition((exp11 AND exp11))
#CSCN2: [6, 1, 0]; INDEX33555480(T1)
57. REMOTE / LSET
实现外部连接(DBLINK)。
REMOTE: 远程查询;
LSET: 远程的结果集