暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

达梦数据库官方文档中关于SQL调优方法论及方法

原创 小小星月明 2022-08-26
3234

SQL 调优

1.1 简介

首先要关注下列几点:

1.达梦数据库安装时的配置参数是否符合应用场景需求;

2.达梦数据库的 INI 配置文件中各项参数是否已经处于最优配置;

3.应用系统中数据库设计是否合理。

1.2 调优目标

SQL 调优的整体目标是使用最优的执行计划,这意味着 IO 以及 CPU 代价最小。具体而言调优主要关注下列方面:

n 表扫描

如果计划中对某大表使用了全索引扫描,那么用户需要关注是否存在着该表的某个查询条件使得过滤后可以淘汰至少一半的数据量。通过添加相应的索引,全索引扫描可能被转换为范围扫描或等值查找。添加的二级索引可以包含该表上所有被选择项以避免 BLKUP2 操作符的查找操作带来的第二次 IO 开销,但无疑这会增加二级索引的大小。用户需权衡二者的利弊以选择正确的处理方式。

n 连接操作的顺序和类型

多表连接时,不同的连接顺序会影响中间结果集数量的大小,这时调优的目标就是要找到一种能使中间结果保持最小的连接顺序。

对于给定的一个连接或半连接,DM 可以用 HASH 连接、嵌套循环连接、索引连接或者是归并连接实现。通过分析表的数据量大小和索引信息,SQL 调优目标是选择最适宜的操作符。

对半连接而言,HASH 连接还可细分为左半 HASH 和右半 HASH。用户可以通过始终对数据量小的一侧建立 HASH 来进行调优。

n 分组操作

分组操作往往要求缓存所有数据以找到属于同一组的所有数据,在大数据量情况下这会

带来大量的 IO。用户应该检查 SQL 查询和表上索引信息,如果可以利用包含分组列的索引,那么执行计划就会使用排序分组从而不用缓存中间结果。

1.3 确定高负载的 SQL

在打开监控开关(ENABLE_MONITOR=1、MONITOR_TIME=1)后,可以通过查询动态视图 V$LONG_EXEC_SQLS V$SYSTEM_LONG_EXEC_SQLS 来确定高负载的 SQL 语句。

例如:

显示最近 1000 条执行时间较长的 SQL 语句

SELECT * FROM V$LONG_EXEC_SQLS;

或者

显示服务器启动以来执行时间最长的20 条 SQL 语句

SELECT * FROM V$SYSTEM_LONG_EXEC_SQLS;

1.4 自动 SQL 调整

使用查询优化向导工具,输入需要进行调整的 SQL 语句,向导工具将在分析完执行计划后给出推荐索引的提示。用户只需按提示建立相应索引即可。

1.5 开发有效的 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 中有记录。调整后的语句执行速度明显比上一句高。

另外,在 DM 的语法里是可以省略 FROM 子句的,这时系统会自动加上 FROM SYSDUAL。因此前面的科学计算例子可以简化为 SELECT 3*4;

4SELECT 项避免‘*’

除非用户确实要选择表中所有列,否则 SELECT *这种写法将让执行器背上沉重的负荷。因为每一列的数据不得不自下往上层层向上传递。不仅仅如此,如果用户查询的是列存储表,

那么列存储所带来的 IO 优势将损耗殆尽。任何时候,用户都要了解表结构和业务需求,小心地选择需要的列并一一给出名称,避免直接用 SELECT *

5.避免功能相似的重复索引

索引并非越多越好。抛开优化器面对众多索引逐一试探所耗费的时间不谈,如果表上增删改操作频繁,那么索引的维护将会成为大麻烦,尤其是函数索引的计算开销更不能忽略。

6.使用 COUNT(*)统计结果行数

如果对单表查询 COUNT(*)且没有过滤条件,那么 DM 优化器会直接读取相关索引中存储的行数信息,加以回滚段中其他事务插入或删除元组的行数修正,迅速地给出最终结果而避免对实际数据的读取。相比之下,COUNT(列名)会对数据进行读操作,执行效率远低于COUNT(*)

即使查询中含有过滤条件,由于 DM 特有的批处理方式,COUNT(*)依旧快于其他写法。这是因为 COUNT(*)无需取得行的具体值而仅仅需要行数这一信息。

需要额外说明的是,COUNT(*)会将 NULL 值计算在内而 COUNT(列名)是不包含 NULL的,因此用户要结合应用场景决定是否可以使用 COUNT(*)

7.使用 EXPLAIN 来查看执行计划

在查询语句或者插入、删除、更新语句前增加 EXPLAIN 关键字,DM 将显示其执行计划无需实际执行它。查阅 V$SQL_NODE_NAME 表中每个操作符的含义,用户可以很方便且直观地了解数据如何被处理及传递。如果启用了统计信息收集,那么对照执行计划和对动态视图 V$SQL_NODE_HISTORYV$SQL_NODE_NAME 的查询结果,用户就可以知道在实际执行中每一个操作符执行的时间,进而找出性能瓶颈。

8UNION 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 中。DM 优化器会判断并自动转换部分等效于 WHERE HAVING 子句,但显式地给出最佳 SQL 语句会让优化器工作得更好。

10.使用优化器提示(HINT


1.6 使用优化器提示

DM 查询优化器采用基于代价的方法。在估计代价时,主要以统计信息或者普遍的数据分布为依据。在大多数情况下,估计的代价都是准确的。但在一些比较特殊的场合,例如缺少统计信息,或统计信息陈旧,或抽样数据不能很好地反映数据分布时,优化器选择的执行计划不是“最优”的,甚至可能是很差的执行计划。

用户可以提供一种方法,指示优化器按照固定的方法去选择 SQL 执行计划。把这种人工干预优化器的方法称为 HINT,它使优化器根据用户的需要来生成指定的执行计划。如果优化器无法生成相应的执行计划,该 HINT 将会被忽略。

HINT 的常见格式如下所示:

SELECT /*+ HINT1 [HINT2]*/ 列名 FROM 表名 WHERE_CLAUSE ;

UPDATE 表名 /*+ HINT1 [HINT2]*/ SET 列名 =变量 WHERE_CLAUSE ;

DELETE FROM 表名 /*+ HINT1 [HINT2]*/ WHERE_CLAUSE ;

需要注意的是:如果 HINT 的语法没有写对或指定的值不正确,DM 并不会报错,而是直接忽略 HINT 继续执行。

可通过 V$HINT_INI_INFO 动态视图查询 DM 支持的 HINT。HINT 参数分为两类,HINT_TYPE OPT”表示分析阶段使用的参数;HINT_TYPE EXEC”表示运行阶段使用的参数,运行阶段使用的参数对于视图无效。

1.6.1 索引提示

1. 使用索引

目前 DM 提供的 HINT 为表索引的选择 HINT,它指示使用指定索引进行数据检索。

语法:

表名 + INDEX + 索引名

/*+ INDEX (表名[,] 索引名) {INDEX (表名[,] 索引名)} */

一个语句中最多指定 8 个索引。在后一种语法格式中,如果查询中给出了表的别名那么必须使用别名。

假设表 t1 上 id 和 name 列上都存在着单列索引。

--数据准备

DROP TABLE T1 CASCADE;

CREATE TABLE T1 (ID INTEGER,NAME VARCHAR(128));

CREATE INDEX IDX_T1_ID ON T1(ID);

CREATE INDEX IDX_T1_NAME ON T1(NAME);

例 1 在查询语句中指定索引。

SELECT * FROM T1 WHERE ID > 2011 AND NAME < 'XXX';

如果 ID 列上能过滤更多数据,建议指示用索引 IDX_T1_ID。

SELECT * FROM T1 INDEX IDX_T1_ID WHERE ID > 2011 AND NAME < 'XXX';

SELECT /*+INDEX(T1, IDX_T1_ID) */ * FROM T1 WHERE ID > 2011 AND NAME < 'XXX';

例 2 当有多个索引时,要指定使执行计划最优的。

SELECT * FROM T1 WHERE ID > 2011 AND NAME < 'XXX' ORDER BY NAME;

考虑到后面的 NAME 列排序操作,建议指示使用 NAME 列的索引 IDX_T1_NAME,因为这样可以在执行过程中省略掉排序操作(执行计划中可以看出来),比使用 ID 列索引代价小。

SELECT * FROM T1 INDEX IDX_T1_NAME WHERE ID > 2011 AND NAME < 'XXX' ORDER BY NAME;

SELECT /*+ INDEX(A IDX_T1_NAME)*/ * FROM T1 A WHERE ID > 2011 AND NAME < 'XXX' ORDER BY NAME;

2. 不使用索引

语法:

/*+ NO_INDEX (表名[,] 索引名) { NO_INDEX (表名[,] 索引名)} */

可以指定多个索引,则这些索引都不能被使用。一个语句中最多指定 8 个索引。

1.6.2 连接方法提示

DBA 可以通过指定两个表间的连接方法来检测不同连接方式的查询效率,指定的连接可能由于无法实现或代价过高而被忽略。如果连接方法提示中的表名(别名)或索引名无效也会被自动忽略。

--数据准备

DROP TABLE T1 CASCADE;

DROP TABLE T2 CASCADE;

CREATE TABLE T1 (ID INTEGER,NAME VARCHAR(128));

CREATE TABLE T2 (ID INTEGER,NAME VARCHAR(128));

begin

for i in 1..1000 loop

insert into T1 values(i,'dameng'||i);

insert into T2 values(i+500,'damengsh'||i);

end loop;

end;

1. USE_HASH

强制两个表间使用指定顺序的哈希连接,例如:

EXPLAIN SELECT /*+ USE_HASH(T1, T2) */ * FROM T1, T2 WHERE T1.id = T2.id;

2. NO_USE_HASH

强制两个表间不能使用指定顺序的哈希连接,例如:

EXPLAIN SELECT /*+ NO_USE_HASH(T1, T2) */ * FROM T1, T2 WHERE T1.id = T2.id;

NO_USE_HASH(T1, T2)表示不允许 T1 作为左表,T2 作为右表的哈希连接,但 T1 作为右表的哈希连接还是允许的。

3. USE_NL

强制两个表间使用嵌套循环连接,例如:

EXPLAIN SELECT /*+ USE_NL(a, b) */ * FROM T1 a, T2 b WHERE a.ID = b.ID;

4. NO_USE_NL

强制两个表间不能使用嵌套循环连接,例如:

EXPLAIN SELECT /*+ NO_USE_NL(a, b) */ * FROM T1 a, T2 b WHERE a.ID = b.ID;

5. USE_NL_WITH_INDEX

当连接情况为左表+右表索引时,强制两个表间使用索引连接,例如:

--数据准备

CREATE INDEX IDX_T2_ID ON T2(ID);

--执行 EXPLAIN

EXPLAIN SELECT /*+ USE_NL_WITH_INDEX(T1, IDX_T2_ID) */ * FROM T1, T2 WHERE T1.ID = T2.ID;

6. NO_USE_NL_WITH_INDEX

当连接情况为左表+右表索引时,强制两个表间不能使用索引连接,例如:

EXPLAIN SELECT /*+ NO_USE_NL_WITH_INDEX(T1, IDX_T2_ID) */ * FROM T1, T2 WHERE T1.ID = T2.ID;

--使用完毕,请删除索引

DROP INDEX IDX_T2_ID;

7. USE_MERGE

强制两个表间使用归并连接。归并连接所用的两个列都必须是索引列。例如:

--数据准备

CREATE INDEX IDX_T1_ID ON T1(ID);

CREATE INDEX IDX_T2_ID ON T2(ID);

STAT 100 ON T1(ID);

STAT 100 ON T2(ID);

--执行 EXPLAIN

EXPLAIN SELECT /*+ USE_MERGE(T1,T2) */ * FROM T1, T2 WHERE T1.ID = T2.ID AND T1.ID < 1 AND T2.ID < 1;

当连接类型为外连接时,无法使用归并连接,此时即使指定 USE_MERGE,也不起作用。

8. NO_USE_MERGE

强制两个表间不能使用归并连接,例如:

EXPLAIN SELECT /*+ NO_USE_MERGE(T1,T2) */ * FROM T1, T2 WHERE T1.ID = T2.ID AND T1.ID > 1 AND T2.ID > 1;

--使用完毕,请删除索引

DROP INDEX IDX_T1_ID;

DROP INDEX IDX_T2_ID;

9. SEMI_GEN_CROSS

优先采用半连接转换为等价的内连接,仅 OPTIMIZER_MODE=1 有效。例如:

EXPLAIN SELECT /*+ SEMI_GEN_CROSS OPTIMIZER_MODE(1) */ COUNT(*) FROM T1 A WHERE A.ID IN (SELECT B.ID FROM T1 B);

10. NO_SEMI_GEN_CROSS

不采用半连接转换为等价的内连接,仅 OPTIMIZER_MODE=1 有效。例如:

EXPLAIN SELECT /*+ NO_SEMI_GEN_CROSS OPTIMIZER_MODE(1) */ COUNT(*) FROM T1 A WHERE A.ID IN (SELECT B.ID FROM T1 B);

11. USE_CVT_VAR

优先采用变量改写方式实现连接,适合驱动表数据量少而另一侧计划较复杂的场景,仅OPTIMIZER_MODE=1 有效。例如:

EXPLAIN SELECT /*+ USE_CVT_VAR OPTIMIZER_MODE(1) */ COUNT(*) FROM T1 A WHERE A.id = 1001 AND EXISTS (SELECT 1 FROM T1 B, T1 C WHERE B.id = C.id AND A.name=B.name);

12. NO_USE_CVT_VAR

不考虑变量改写方式实现连接,仅 OPTIMIZER_MODE=1 有效。例如:

EXPLAIN SELECT /*+ NO_USE_CVT_VAR OPTIMIZER_MODE(1) */ COUNT(*) FROM T1 A WHERE A.id = 1001 AND EXISTS (SELECT 1 FROM T1 B, T1 C WHERE B.id = C.id AND A.name=B.name);

13. ENHANCED_MERGE_JOIN

一般情况下,归并连接需要左右孩子的数据按照连接列有序,使用此优化器提示时,优化器将考虑通过插入排序操作符的方式实现归并连接,仅 OPTIMIZER_MODE=1 有效。例如:

EXPLAIN SELECT /*+ stat(T1 1M) stat(T2 1M) */COUNT(*) FROM T1, T2 WHERE

T1.NAME=T2.NAME AND T1.ID=T2.ID;

--不加 hint 时计划,使用了哈希连接

-- 加 HINT 后计划,通过增加排序以使用了归并连接

1.6.3 连接顺序提示

多表连接时优化器会考虑各种可能的排列组合顺序。使用 ORDER HINT 指定连接顺序提示可以缩小优化器试探的排列空间,进而得到接近 DBA 所期望的查询计划。如果连接顺序

和连接方法提示同时指定且二者间存在自相矛盾,优化器会以连接顺序提示为准。

ORDER HINT

语法:/*+ ORDER (T1, T2 , T3, … tn ) */

本节中的例子用到 4 个表 T1, T2 , T3, T4。

CREATE TABLE T1(C1 INT,C2 VARCHAR);

CREATE TABLE T2(D1 INT,D2 VARCHAR);

CREATE TABLE T3(E1 INT,E2 VARCHAR);

CREATE TABLE T4(F1 INT,F2 VARCHAR);

例 :

SELECT * FROM t1, T2 , T3, T4 WHERE …

如果期望表的连接顺序是 T1, T2, T3,那么可以加入这样的提示:

SELECT /*+ ORDER(T1, T2, T3 )*/* FROM T1, T2 , T3, T4 WHERE …

在指定上述连接顺序后,T4,T1,T2,T3 或 T1,T2,T4,T3 会被考虑;T3,T1,T2 或T1,T3,T2 不被考虑。


连接顺序也可以和连接方法同时指定用于得到更特定的执行计划。

例:

EXPLAIN SELECT /*+ OPTIMIZER_MODE(1), ORDER(T1,T2,T3,T4) ,USE_HASH(T1,T2),USE_HASH(T2,T3), USE_HASH(T3,T4)*/* FROM T1,T2,T3,T4 WHERE T1.c1=T2.d1 AND T2.d2 = T3.e2 AND T3.e1 = T4.f1;


1.6.4 统计信息提示

优化器在计划优化阶段会自动获取基表的行数。但是一些特殊类型的表行数估算并不准确,或者 DBA 希望了解表大小对计划影响的时候,需要手动设置表的行数。

语法:/*+ STAT (表名, 行数) */

统计信息提示只能针对基表设置,视图和派生表等对象设置无效。如果表对象存在别名则必须使用别名。行数只能使用整数,或者整数+K(千),整数+M(百万),整数+G(十亿)行数提示设置后,统计信息的其它内容也会做相应的调整。

例 :

CREATE TABLE T_S(C1 INT);

INSERT INTO T_S SELECT LEVEL FROM DUAL CONNECT BY LEVEL<= 100;

COMMIT;

STAT 100 ON T_S(C1);

EXPLAIN SELECT /*+ STAT(T_S,1M) */ * FROM T_S WHERE C1 <= 10;

 

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论