一. 优化器概述
数据库管理系统的优化器是决定 SQL 执行性能的关键。DM 采用基于代价的优化器,服务器接收到 SQL 语句后,经过语法分析和语义分析,之后进行各种关系代数转换,形成一 系列备选的逻辑执行计划。DM 优化器通过对这些备选逻辑计划进行代价估算和优化,最终 生成一个最优的物理执行计划,交由执行器执行。
优化器工作流程前面说的“生成一个最优的物理执行计划”只是一个相对的概念,计算出一个与实际执 行相接近的代价值是一件困难的事。影响实际执行代价的因素非常多,优化器不可能也没有 必要非常全面地考虑每一个细节,如系统封锁,并发等因素。DM 优化器主要关注的是执行 查询所涉及的表的记录行数,数据页的数量,可利用的索引和统计信息,以及内存,IO 和 CPU 的计算量等
二. SQL执行顺序
1、FROM(从)子句指定要查询的表或视图。这里可能包含多个表名,用逗号分隔。如果有连接条件,则需要使用 JOIN 关键字将不同的表进行连接。
示例:SELECT * FROM table_name;
2、WHERE(条件)子句用于添加筛选条件,只返回符合特定条件的记录。通常与比较运算符(=、<、>等)结合使用。
示例:SELECT * FROM table_name WHERE column = value;
3、GROUP BY(按组分组)子句根据指定列对结果集进行分组。通常与聚合函数(SUM、COUNT、AVG等)一起使用。
示例:SELECT column1, SUM(column2) FROM table_name GROUP BY column1;
4、HAVING(具体条件)子句类似于WHERE子句,但是HAVING子句主要用于GROUP BY后面的筛选条件。
示例:SELECT column1, COUNT() FROM table_name GROUP BY column1 HAVING COUNT() > 5;
5、ORDER BY(排序)子句用于对结果集进行排序,默认为升序。也可以指定DESC(降序)。
示例:SELECT * FROM table_name ORDER BY column ASC/DESC;
6、LIMIT(限制)子句用于限制返回的记录数量。通常与OFFSET(偏移量)一起使用。
示例:SELECT * FROM table_name LIMIT number OFFSET offset;
7、UNION(联合)子句用于将两个或更多 SELECT 语句的结果集合并到一个结果集中。注意,每个 SELECT 语句必须具有相同的列数和数据类型。
示例:SELECT column1 FROM table1 UNION SELECT column1 FROM table2;
8、DISTINCT(去重)关键字用于消除结果集中的重复值。
示例:SELECT DISTINCT column FROM table_name;
9、AS(别名)关键字用于为列或表设置别名,提高查询效率和可读性。
示例:SELECT column AS alias FROM table_name;
10、JOIN(连接)关键字用于将多个表基于共同的列进行连接。
示例:SELECT t1.column1, t2.column2 FROM table1 t1 INNER JOIN table2 t2 ON t1.id = t2.table1_id;
三. 执行计划操作符
达梦执行计划涉及到的一些主要操作符有:
CSCN :基础全表扫描(a),从头到尾,全部扫描
SSCN :二级索引扫描(b), 从头到尾,全部扫描
SSEK :二级索引范围扫描(b) ,通过键值精准定位到范围或者单值
CSEK :聚簇索引范围扫描© ,通过键值精准定位到范围或者单值
BLKUP :根据二级索引的ROWID 回原表中取出全部数据(b + a)
四. 执行计划解读
- 简单查询操作符
SQL> explain select * from student;
1 #NSET2: [1, 8, 125]
2 #PRJT2: [1, 8, 125]; exp_num(5), is_atom(FALSE)
3 #CSCN2: [1, 8, 125]; INDEX33555473(STUDENT)
NSET2:返回的结果集,1毫秒代价,8行数据,每行125个字节;
PRJT2:投影数据,exp_num总计5列;
CSCN2:聚集索引全表扫描表,INDEX使用的索引。 - 复杂查询操作符
查询和"01"号的同学学习的课程完全相同的其他同学的信息
SQL> explain select * from student where s_id in (select s_id from score s inner join (select c_id from score where s_id = 1 ) t1 on s.c_id = t1.c_id where s_id != 1 group by s_id having count() = (select count() from score where s_id = 1));
1 #NSET2: [2, 1, 125]
2 #PIPE2: [2, 1, 125]
3 #PRJT2: [2, 1, 125]; exp_num(5), is_atom(FALSE)
4 #HASH LEFT SEMI JOIN2: [2, 1, 125]; KEY_NUM(1); KEY(STUDENT.S_ID=DMTEMPVIEW_889193478.colname) KEY_NULL_EQU(0)
5 #CSCN2: [1, 8, 125]; INDEX33555473(STUDENT)
6 #PRJT2: [1, 1, 16]; exp_num(1), is_atom(FALSE)
7 # SLCT2: [1, 1, 16]; DMTEMPVIEW_889193476.TMPCOL1 = var1
8 #PRJT2: [1, 7, 16]; exp_num(2), is_atom(FALSE)
9 #SAGR2: [1, 7, 16]; grp_num(1), sfun_num(1), distinct_flag[0]; slave_empty(0) keys(S.S_ID)
10 #SORT3: [1, 7, 16]; key_num(1), is_distinct(FALSE), top_flag(0), is_adaptive(1)
11 #SLCT2: [1, 15, 16]; SCORE.S_ID = 1
12 #HASH2 INNER JOIN: [1, 15, 16]; KEY_NUM(1); KEY(S.C_ID=SCORE.C_ID) KEY_NULL_EQU(0)
13 #NEST LOOP INDEX JOIN2: [1, 15, 16]
14 #ACTRL: [1, 15, 16];
15 #SLCT2: [1, 15, 8]; S.S_ID <> 1
16 #SSCN: [1, 15, 8]; INDEX33555487(SCORE as S)
17 # SSEK2: [1, 1, 8]; scan_type(ASC), INDEX33555487(SCORE), scan_range[(1,S.C_ID),(1,S.C_ID)]
18 #SSCN: [1, 18, 8]; INDEX33555487(SCORE)
19 #SPL2: [1, 1, 4]; key_num(1), spool_num(0), is_atom(TRUE), has_var(0), sites(-)
20 #PRJT2: [1, 1, 4]; exp_num(1), is_atom(TRUE)
21 #AAGR2: [1, 1, 4]; grp_num(0), sfun_num(1), distinct_flag[0]; slave_empty(0)
22 #SSEK2: [1, 3, 4]; scan_type(ASC), INDEX33555487(SCORE), scan_range[(1,min),(1,max))
NSET2:返回结果集
HASH LEFT SEMI JOIN2:哈希左半连接
SLCT2:带有过滤条件,通指where
SAGR2:流分组聚集,带索引的group by分组
SORT3:排序
SSCN:索引全扫描
SPL2:生成了临时表
AAGR2:简单聚集
NEST LOOP INDEX JOIN2:嵌套索引连接
HASH2 INNER JOIN:哈希内连接
SSEK2:二级索引扫描
五. 多表连接方式
- 嵌套循环连接
驱动表返回多少条记录,被驱动表就访问多少次!
其中例子:Select/+ leading(t1) use_nl(t2)/ from t1,t2 where t1.id = t2.id;
Use_nl表示强制使用嵌套循环连接的方式,leading(t1)表示强制先访问t1表,也就是t1表作为驱动表。
不会产生排序操作。支持所有的连接条件,没有任何限制。
嵌套循环连接适用场景:
A. 两表关联返回的记录不多,最佳情况是驱动表结果集仅返回1条或者少量几条记录,而被驱动表仅匹配到1条或少量几条记录,这种情况即便T1表和T2表的
B. 遇到一些不等值查询导致哈希连接和排序合并连接被限制使用。
优化思路:
驱动表的限制条件所在列有索引,被驱动表的连接条件的列有索引。
例如:select * from t1,t2 where t1.id = t2.t1_id and t1.n = 19;
此时如果t1表在n字段(限制条件)有索引,t2表在t1_id字段(连接条件)有索引的话,会高效。
解释:驱动表的限制条件建了索引,会快速的返回1条或几条记录,然后再根据连接条件传递给被驱动表,而被驱动表此时在该字段建有索引就会快速的返回记录。 - 哈希连接
在HASH连接中,驱动表和被驱动表都只会访问0次或者1次。
Select /+ leading(t1) use_hash(t2)/* from t1,t2 where t1.id=t2.id;
哈希连接并不排序,但是需要消耗内存用于建立HASH表。在获取字段中根据业务需求尽量少获取字段。
哈希连接不支持不等值连接<>,不支持>和不支持<的连接方式,也不支持like的连接方式。
优化思路:
A. 在限制条件列如有适当的索引可以提升性能
B. 增大HASH_JOIN_SIZE,一般在内存自动管理中。 - 排序合并连接
和HASH连接一样,只访问0次或者1次。但是没有驱动和被驱动表的概念。
Select /+ ordered use_merge(t2)/* from t1,t2 where t1.id= t2.id;
排序合并连接需要排序,会消耗内存。在获取字段中根据业务需求尽量少获取字段。
排序合并连接不支持<>,like的连接条件。
嵌套循环连接和哈希连接有驱动顺序,驱动表的顺序不同将影响表连接的性能,而排序合并连接没有驱动的概念,无论哪张表在前都无妨。
优化思路:
在连接条件列建立索引,以消除一张表的排序,提升效率。(但是2张表同时建立索引也只会消除一个表的排序)
索引连接是一种在数据库查询中常用的优化技术,它利用索引来提高表之间连接操作的效率。当两个或多个表需要根据某些条件进行连接时,索引连接能够显著减少搜索和匹配所需的时间。 - 索引连接
4.1 工作原理
选择驱动表:在执行索引连接之前,数据库优化器会选择一个表作为驱动表(通常是较小的表或结果集中行数较少的表)。
扫描驱动表:数据库系统会顺序或根据某种策略(如索引顺序)扫描驱动表中的行。
使用索引查找匹配行:对于驱动表中的每一行,数据库系统会使用被连接表上的索引来快速查找满足连接条件的匹配行。索引允许数据库系统直接定位到匹配的行,而无需扫描整个表。
结果组合:找到匹配的行后,数据库系统会将它们与驱动表中的当前行组合起来,形成查询结果的一部分。
继续扫描:数据库系统继续扫描驱动表的下一行,并重复上述过程,直到扫描完驱动表的所有行。
4.2 性能考虑与优化
索引选择:索引连接的性能高度依赖于所选择的索引。为了获得最佳性能,应该确保被连接表上的连接条件列有适当的索引,并且索引的选择应该基于查询的过滤性和选择性。
表顺序:虽然索引连接可以从任何表开始,但选择较小的表或结果集中行数较少的表作为驱动表通常更有效。这样可以减少需要扫描和匹配的行数。
索引覆盖:如果索引包含了查询所需的所有列(即覆盖索引),那么数据库系统可以避免回表操作,进一步提高性能。回表操作是指在使用索引找到匹配的行后,还需要访问表中的数据页来获取其他列的值。
统计信息:数据库优化器使用统计信息来选择最佳的查询执行计划。确保统计信息是最新的,并且准确地反映了表的大小、行数、列的分布等特征,有助于优化器做出更好的决策。
并行处理:对于大型查询,可以考虑使用并行处理来提高索引连接的性能。通过将查询拆分成多个部分并在多个处理器或线程上同时执行,可以加快查询的执行速度。
需要注意的是,索引连接并不总是最佳的选择。在某些情况下,其他连接策略(如哈希连接或嵌套循环连接)可能更有效。数据库优化器会根据查询的具体情况和表的统计信息来选择最合适的连接策略。