SQL 调优作为数据库性能调优中的最后一个环节,对查询性能产生着直接的影响。在进
行正式的 SQL 调优前,用户首先要关注下列几点:
1.达梦数据库安装时的配置参数是否符合应用场景需求;
2.达梦数据库的 INI 配置文件中各项参数是否已经处于最优配置;
3.应用系统中数据库设计是否合理。
本章将介绍定位高负载的 SQL 语句的方法,利用自动 SQL 调整功能进行优化,以及如
何开发有效的 SQL 语句和使用优化器提示来影响执行计划。
调优目标
SQL 调优的整体目标是使用最优的执行计划,这意味着 IO 以及 CPU 代价最小。具体而
言调优主要关注下列方面:
表扫描
如果计划中对某大表使用了全索引扫描,那么用户需要关注是否存在着该表的某个查询
条件使得过滤后可以淘汰至少一半的数据量。通过添加相应的索引,全索引扫描可能被转换
为范围扫描或等值查找。添加的二级索引可以包含该表上所有被选择项以避免 BLKUP2 操作
符的查找操作带来的第二次 IO 开销,但无疑这会增加二级索引的大小。用户需权衡二者的
利弊以选择正确的处理方式。
连接操作的顺序和类型
多表连接时,不同的连接顺序会影响中间结果集数量的大小,这时调优的目标就是要找
到一种能使中间结果保持最小的连接顺序。
对于给定的一个连接或半连接,DM7 可以用 HASH 连接、嵌套循环连接、索引连接或者
是归并连接实现。通过分析表的数据量大小和索引信息,SQL 调优目标是选择最适宜的操作
符。
对半连接而言,HASH 连接还可细分为左半 HASH 和右半 HASH。用户可以通过始终对数
据量小的一侧建立 HASH 来进行调优。
分组操作
分组操作往往要求缓存所有数据以找到属于同一组的所有数据,在大数据量情况下这会
带来大量的 IO。用户应该检查 SQL 查询和表上索引信息,如果可以利用包含分组列的索引,
那么执行计划就会使用排序分组从而不用缓存中间结果。
确定高负载的 SQL
在打开监控开关(ENABLE_MONITOR=1、MONITOR_TIME=1)后,可以通过查询动态
视图 V$LONG_EXEC_SQLS 或 V$SYSTEM_LONG_EXEC_SQLS 来确定高负载的 SQL 语句。
前者显示最近 1000 条执行时间较长的 SQL 语句,后者显示服务器启动以来执行时间最长的
20 条 SQL 语句。例如
SELECT * FROM V$LONG_EXEC_SQLS;
或者
SELECT * FROM V$SYSTEM_LONG_EXEC_SQLS;
自动 SQL 调整
使用查询优化向导工具,输入需要进行调整的 SQL 语句,向导工具将在分析完执行计划
后给出推荐索引的提示。用户只需按提示建立相应索引即可。
有效sql语句调优建议
1.避免使用 OR 子句
OR 子句在实际执行中会被转换为类似于 UNION 的查询。如果某一个 OR 子句不能利用
上索引则会使用全表扫描造成效率低下,应避免使用。
如果 OR 子句都是对同一列进行过滤,用户可以考虑使用 IN VALUE LIST 的过滤形式。
如:
SELECT ... WHERE CITY = 'SHANGHAI' OR CITY = 'WUHAN' OR CITY = 'BEIJING';
调整为
SELECT ... WHERE CITY IN( 'SHANGHAI','WUHAN','BEIJING');
2.避免使用困难的正则表达式
在 SQL 语言中,LIKE 关键字支配通配符匹配,含通配符的表达式被称为正则表达式。
有的正则表达式可以自动优化为非匹配的。例如:a LIKE 'L%'可以优化为 a>='L' AND a
<'M',这样就可以用到 a 上的索引。即使没有索引,转换后的比较也更快。再如:a LIKE
'LM_'可以转化为 a>='LM' AND a<'LN' AND a LIKE 'LM_'。虽然仍然包含着通配符
匹配,但大大缩小了匹配的范围。
所谓困难的正则表达式是指开头和结尾都为通配符的正则表达式,如'_L%'、'%L_',
优化器没办法缩小它们的匹配范围,也不可能用到索引而必须使用全表扫描。因此要尽可能
避免这样的正则表达式。
如果仅仅是开头为通配符,用户可以在列 a 上建立 REVERSE(a)这样一个函数索引,利
用函数索引反转待匹配项从而使用函数索引进行范围扫描。
3.灵活使用伪表(SYSDUAL)
首先可以利用伪表进行科学计算,执行语句 SELECT 3*4 FROM SYSDUAL,则可以得
到结果 12;
其次,在某些方面使用 SYSDUAL 可提高效率。例如:查询过程中要判断表 t1 中是否有
满足 condition1 条件的记录存在,可执行以下语句:
SELECT COUNT(*) INTO x FROM t1 WHERE condition1;
然后根据变量 x 的取值来判断。但是当 t1 非常大时该语句执行速度很慢,而且由于不
知道 SELECT 返回的个数,不能用 SELECT *代替。事实上这个查询可以利用伪表来完成:
SELECT 'A' INTO y FROM SYSDUAL
WHERE EXISTS (SELECT 1 FROM t1 WHERE condition1);
判断 y 值,如等于'A'则 T1 中有记录。调整后的语句执行速度明显比上一句高。
另外,在 DM7 的语法里是可以省略 FROM 子句的,这时系统会自动加上 FROM SYSDUAL。
因此前面的科学计算例子可以简化为 SELECT 3*4;
4.SELECT 项避免„*‟
除非用户确实要选择表中所有列,否则 SELECT *这种写法将让执行器背上沉重的负荷。
因为每一列的数据不得不自下往上层层向上传递。不仅仅如此,如果用户查询的是垂直分区
表,那么更大的麻烦在于垂直分区表的所有子表都要参与连接操作;如果用户查询的是列存
储表,那么列存储所带来的 IO 优势将损耗殆尽。
任何时候,用户都要了解表结构和业务需求,小心地选择需要的列并一一给出名称,避
免直接用 SELECT *。
5.避免功能相似的重复索引
索引并非越多越好。抛开优化器面对众多索引逐一试探所耗费的时间不谈,如果表上增
删改操作频繁,那么索引的维护将会成为大麻烦,尤其是函数索引的计算开销更不能忽略。
6.使用 COUNT(*)统计结果行数
如果对单表查询 COUNT(*)且没有过滤条件,那么 DM7 优化器会直接读取相关索引中存
储的行数信息,加以回滚段中其他事务插入或删除元组的行数修正,迅速地给出最终结果而
避免对实际数据的读取。相比之下,COUNT(列名)会对数据进行读操作,执行效率远低于
COUNT(*)。
即使查询中含有过滤条件,由于 DM7 特有的批处理方式,COUNT(*)依旧快于其他写法。
这是因为 COUNT(*)无需取得行的具体值而仅仅需要行数这一信息。
需要额外说明的是,COUNT(*)会将 NULL 值计算在内而 COUNT(列名)是不包含 NULL
值的,因此用户要结合应用场景决定是否可以使用 COUNT(*)。
7.使用 EXPLAIN 来查看执行计划
在查询语句或者插入、删除、更新语句前增加 EXPLAIN 关键字,DM7 将显示其执行计
划而无需实际执行它。查阅 V$SQL_NODE_NAME 表中每个操作符的含义,用户可以很方便
且直观地了解数据如何被处理及传递。如果启用了统计信息收集,那么对照执行计划和对动
态视图 V$SQL_NODE_HISTORY,V$SQL_NODE_NAME 的查询结果,用户就可以知道在实际
执行中每一个操作符执行的时间,进而找出性能瓶颈。
8.UNION 和 UNION ALL 的选择
UNION 和 UNION ALL 的区别是前者会过滤掉值完全相同的元组,为此 UNION 操作符
需要建立 HASH 表缓存所有数据并去除重复,当 HASH 表大小超过了 INI 参数指定的限制时
还会做刷盘。
因此如果应用场景并不关心重复元组或者不可能出现重复,那么 UNION ALL 无疑优于
UNION。
9.优化 GROUP BY ... HAVING
GROUP BY 最常见的实现有 HASH 分组(HAGR)和排序分组(SAGR)。前者需要缓存中
间结果;如果用户在 GROUP BY 的列上建立索引,那么优化器就会判断并可能使用上该索引,
这时的 GROUP BY 就会变为 SAGR。
HAVING 是分组后对结果集进行的过滤,如果过滤条件无关集函数操作,用户可以考虑
将过滤条件放在WHERE而不是HAVING中。DM7优化器会判断并自动转换部分等效于WHERE
的 HAVING 子句,但显式地给出最佳 SQL 语句会让优化器工作得更好。
10.使用优化器提示(HINT)
利用经验对优化器的计划选择进行调整,HINT 是 SQL 调整不可或缺的一步。
更多内容,请访问