数据库执行一条语句有多种方式,为了选择最优的执行方式,产生了查询优化器。查询优化器分析语句运行时的所有因素,选择最优的方式去执行,提高了查询效率。因此,查询优化是数据库执行 SQL 语句的重要过程,决定了数据库的查询性能。
遇到数据库性能问题,一般从操作系统、实例、SQL 三个方面进行分析。
达梦数据库查询优化器的优化目标为最快响应时间。
通过设置参数 FIRST_ROWS 来决定优先返回多少条记录给用户,而不需要等待全部结果确定后再输出,FIRST_ROWS 设置范围为 1~1000,单位为行。例如:FIRST_ROWS = 10,意思是查询出 10 条结果就立即返回给用户。可以根据实际情况,调整参数值。
查询优化器通过分析可用的执行方式和查询所涉及的对象统计信息来生成最优的执行计划。此外,如果存在 HINT 优化提示,优化器还需要考虑优化提示的因素。
查询优化器的处理过程包括:
(1)优化器生成所有可能的执行计划集合;
(2)优化器基于字典信息的数据分布统计值、执行语句涉及到的表、索引和分区的存储特点来估算每个执行计划的代价。代价是指 SQL 语句使用某种执行方式所消耗的系统资源的估算值。其中,系统资源消耗包括 I/O、CPU 使用情况、内存消耗等;
(3)优化器选择代价最小的执行方式作为该条语句的最终执行计划。
上述过程可简单概括成三个操作:查询转换、估算代价、生成计划。
查询转换是指把经过语法、语义分析的查询块之间的连接类型、嵌套关系进行调整,生成一个更好的查询计划。常用的查询转换技术包括过滤条件的下放、相关子查询的去相关性。
估算代价是指对执行计划的成本进行估算。执行节点之间的代价值相关性较强,一个执行节点的代价包括该节点包含的子节点代价。
代价衡量指标包括选择率、基数、代价。
选择率是指满足条件的记录占总记录数的百分比。 记录集可以是基表、视图、连接或分组操作的结果集。选择率和查询谓词相关或者是谓词的连接相关。一个谓词可以看作是一个过滤器,过滤掉结果集中不满足条件的记录。选择率的范围从 0 到 1。其中,0 表示没有记录被选中,1 表示行集中所有记录都被选中。
如果没有统计信息,则优化器依据过滤条件的类型来设置对应的选择率。例如,等值条件的选择率低于范围条件选择率。这些假定是根据经验值,认为等值条件返回的结果集最少。
如果有统计信息,则可以使用统计信息来估算选择率。
基数是指整个行集的行数,该行集可以是基表、视图、连接或分组操作的结果集。
代价表示资源的使用情况。 查询优化器使用磁盘 I/O、CPU 占用和内存使用作为代价计算的依据,所以代价可以用 I/O 数、CPU 使用率和内存使用一组值来表示。所有操作都可以进行代价计算,例如扫描基表、索引扫描、连接操作或者对结果集排序等。
对单表过滤:定位查找、索引扫描。
对多表连接:连接顺序、连接方式。
影响代价的因素:
生成计划指计划生成器对给定的查询按照连接方式、连接顺序、访问路径生成不同的执行计划,选择代价最小的一个作为最终的执行计划。
连接顺序:指不同连接项的处理顺序。连接项可以是基表、视图、或者是一个中间结果集。一个查询语句可能的计划数量是与 FROM 语句中连接项的数量成正比的,随着连接项的数量增加而增加。
访问路径:决定了从一个基表中获取数据所需要的代价。访问路径可以是基表扫描、索引扫描等。在进行基表扫描或索引扫描时,一次 I/O 读多个页,所以,基表扫描或索引全扫描的代价依赖于表的数据页数和多页读的参数值。二级索引扫描的代价依赖于 B 树的层次、需扫描的叶子块树以及根据 rowid 访问聚集索引的记录数。
连接代价:是指访问两个连接的结果集代价与连接操作的代价之和。
访问路径指从数据库中检索数据的方法。一般情况下,索引访问用于检索表的小部分数据,全表扫描用于访问表的大部分数据。OLTP 应用中,一般使用索引访问路径,因为 OLTP中包含了许多高选择率的 SQL 语句。而决策支持系统则倾向于执行全表扫描来获取数据。
从数据库中定位和检索数据的方法有:全表扫描、聚集索引扫描、二级索引扫描等。
全表扫描:是指从基表中检索数据时,扫描该表中所有的数据。全表扫描方式适合检索表中大部分数据,这时比索引扫描更加有效率。
索引扫描:是指通过指定语句中的索引列进行遍历来检索表中的数据。索引扫描是从基于一列或多列的索引中检索数据。索引不仅包含索引值,还包含对应表中数据的 ROWID。如果需要访问的不是索引列,这时需要通过 ROWID 或聚集索引来找到表中的数据行。
索引扫描:包含聚集索引扫描和二级索引扫描。由于在聚集索引中,包含了表中所有的列值,所以检索数据时只需要扫描这一个索引就可以得到所有需要的数据。如果是二级索引,由于只包含索引列以及对应的 ROWID,如果查询列不在二级索引中则还需要扫描聚集索引来得到所需要的数据。
查询优化器选择访问路径基于以下几个因素:
(1)执行语句中可能的访问路径;
(2)估算每条执行路径的代价。 为了选择一个访问路径,优化器首先会通过检查语句中的 FROM 子句和 WHERE 子句中的条件表达式来决定哪一个访问路径可以使用。优化器会根据可用的访问路径生成可能的执行计划集合,然后使用索引、列和表的统计信息来估算每个计划的代价。最后,优化器选择最小代价的那个执行计划。
影响优化器选择访问路径的因素有语句中的提示(HINT)和统计信息。 用户可以在执行的语句中使用 HINT 来指定访问路径。而统计信息会根据表中数据的分布情况决定采用哪个访问路径会产生最小的代价。
查询语句中 FROM 子句包含多个表时,我们称为连接查询。如 SELECT * FROM t1,t2 就是连接查询。
生成连接查询的执行计划,需要考虑访问路径、连接方式、连接顺序三方面因素。
(1)访问路径
对于每张表采用何种方式来获取数据。例如:全表扫描、索引扫描等。
查询优化器会估算每种扫描方式的代价,选择代价较小的访问路径。
(2)连接方式
确定两张表之间采用哪种连接方式。例如:哈希连接、嵌套连接、归并连接、外连接。
等值连接条件一般会选择哈希连接;非等值连接条件会采用嵌套连接;连接列均为索引列时,会采用归并连接。
嵌套连接:两张表进行非等值连接时会选择嵌套连接。相当于两张表进行笛卡尔集操作。此时,优化器会选择一张代价较小的表作为外表(驱动表),另一张表作为内表,外表的每条记录与内表进行一次连接操作。
哈希连接:两张表进行等值连接时会选择哈希连接。以一张表的连接列为哈希键,构造哈希表,另张表的连接列进行哈希探测,找到满足条件的记录。由于哈希命中率高,因此,在大数据量情况下,哈希连接的效率较高。哈希连接的代价是建立哈希表和哈希探测的代价。
归并连接:两张表的连接列均为索引列,则可以按照索引顺序进行归并,一趟归并就可以找出满足条件的记录。如果查询列也属于索引列的子集,则归并连接只需扫描索引,会有更好的性能表现。在两表连接条件不是等值(如<,<=,>,>=)情况下时,归并排序连接很有用。
外连接:外连接分为左外连接、右外连接、全外连接。作为外表的数据会全部返回,如果没有与外表匹配的记录,则填充 NULL 值。右外连接与左外连接的处理过程类似,只是外表不同,一个是左表,一个是右表。全外连接是进行左外连接和右外连接,返回两次外连接的 union 结果集。
子查询会转换成半连接。共有四种半连接方式:哈希半连接、索引半连接、嵌套半连接、归并半连接。等值连接条件会选择哈希\索引\归并半连接,非等值连接条件会选择嵌套半连接。
哈希半连接:以外表的连接列为 KEY 构造哈希表,内表的连接列进行探测来查找满足连接条件的记录;
索引半连接:如果子查询的连接列为索引前导列,可采用索引半连接。处理过程为外表的数据对子查询使用索引查找,返回满足条件的记录;
归并半连接:如果相关子查询的连接条件列均为索引列,可采用归并半连接。按照索引顺序,对外表、内表进行同步扫描,返回满足条件的记录;
嵌套半连接:如果连接条件为非等值,可转换为嵌套半连接。处理过程为外表的每条记录去遍历内表,返回满足条件的记录。
(3)连接顺序
当超过 2 张表进行连接时,就需要考虑表之间的连接顺序。不合适的连接顺序对执行效率有较大影响。一般原则是,经过连接可以产生较小结果集的表优先处理。
一个连接查询通常会对应多个执行计划,查询优化器会根据优化规则、代价估算挑选最优的执行计划。
对象统计信息描述数据是如何在数据库中存储的。统计信息是优化器的代价计算的依据,可以帮助优化器较精确地估算成本,对执行计划的选择起着至关重要的作用。
达梦数据库的统计信息分三种类型:表统计信息、列统计信息、索引统计信息。
通过直方图来表示。
统计信息生成过程分以下三个步骤:
(1)确定采样的数据:根据数据对象,确定需要分析哪些数据。
(2)确定采样率:根据数据对象的大小,通过内部算法,确定数据的采样率。
采样率与数据量成反比。
(3)生成直方图:根据算法分析表的数据分布特征,确定直方图的类型。
有两种类型的直方图:频率直方图和等高直方图。
频率直方图的每个桶(保存统计信息的对象)的高度不同,等高直方图每个桶的高度相同。例如,对列生成统计信息,当列值分布比较均匀时,会采用等高直方图,否则,采用频率直方图。
在执行查询时,如果数据对象存在统计信息,代价算法可以根据统计信息中的数据,比较精确地计算出操作所需花费的成本,以此来确定连接方式、对象访问路径、连接顺序,选择最优的执行计划。
用户也可以通过修改 OPTIMIZER_DYNAMIC_SAMPLING 参数值在缺乏统计信息时进行动态统计信息收集。
执行计划是 SQL 语句的执行方式,由查询优化器为语句设计的执行方式,交给执行器去执行。在 SQL 命令行使用 EXPLAIN 可以打印出语句的执行计划。
建表和建索引语句:
CREATE TABLE T1(C1 INT,C2 CHAR);
CREATE TABLE T2(D1 INT,D2 CHAR);
CREATE INDEX IDX_T1_C1 ON T1(C1);
INSERT INTO T1 VALUES(1,'A'),(2,'B'),(3,'C'),(4,'D');
INSERT INTO T2 VALUES(1,'A'),(2,'B'),(5,'C'),(6,'D');
打印执行计划:
EXPLAIN SELECT A.C1+1,B.D2 FROM T1 A, T2 B WHERE A.C1 = B.D1;
该计划的大致执行流程如下:
执行过程为:控制流从上向下传递,数据流从下向上传递。
NSET:收集结果集
用于结果集收集的操作符,一般是查询计划的顶层节点。
PRJT:投影
关系的“投影”(project)运算,用于选择表达式项的计算;广泛用于查询,排序,函数索引创建等。
SLCT:选择
关系的“选择” 运算,用于查询条件的过滤。
AAGR:简单聚集
用于没有group by的count sum age max min等聚集函数的计算。
FAGR:快速聚集
用于没有过滤条件时从表或索引快速获取MAX/MIN/COUNT值;DM数据库是世界上单表不带过滤条件下取COUNT值最快的数据库。
HAGR:HASH分组聚集
用于分组列没有索引只能走全表扫描的分组聚集,C2列没有创建索引。
SAGR:流分组聚集
用于分组列是有序的情况下,可以使用流分组聚集,C1上已经创建了索引,SAGR2性能优于HAGR2。
BLKUP:二次扫描
先使用2级别索引定位,再根据表的主键、聚集索引、rowid等信息定位数据行。
CSCN:全表扫描
CSCN2是CLUSTER INDEX SCAN的缩写即通过聚集索引扫描全表,全表扫描是最简单的查询,如果没有选择谓词,或者没有索引可以利用,则系统一般只能做全表扫描。在一个高并发的系统中应尽量避免全表扫描。
SSEK CSEK SSCN:索引扫描
SSEK2是二级索引扫描即先扫描索引,再通过主键、聚集索引、ROWID等信息去扫描表 CSEK2是聚集索引扫描只需要扫描索引,不需要扫描表 SSCN是索引全扫描,不需要扫描表。
在 DM 优化器进行代价估算时,如果子节点是一个复杂查询,可能使得对于子节点的代价估算不准确,导致最终选择的计划在执行时并非最优计划。因此,DM 引入了自适应计划机制。
将 INI 参数 OPTIMIZER_MODE 和 ADAPTIVE_NPLN_FLAG 都置为 1,启用自适应计划机制。此时,优化器会自动判断在某些情况下,复杂查询的子节点可能导致代价估算不准确,则会为该节点生成一个备用计划节点。在实际执行到该节点时,根据准确的代价信息确定是否需要采用备用计划。
执行计划中有一个 ACTRL 操作符,说明优化器为这一条 SQL 语句生成了备用计划。ACTRL 是控制备用计划转换的操作符。 ACTRL 操作符计算下层孩子节点的代价,决定采用默认主计划还是备用计划。需要说明的是,MPP 和并行查询不支持自适应计划。
为了提高查询效率,用户一般会在表中创建索引。查询中的条件列为索引列时,如果索引扫描代价最小,优化器就会采用索引扫描。
索引扫描有多种方式,例如,索引等值查询、索引范围查询。如果查询列属于索引列的子集,则通过索引扫描就可以获得数据,否则,还需要根据 ROWID 或者 PK 在聚集索引中定位记录。
常用的索引类型有唯一索引、组合索引、函数索引。 各自有不同的使用场景。
并行查询(Parallel Query,PQ)是一种多个线程或进程间协作、共同完成 SQL 计 划的优化技术。
根据参与执行的 DM 服务器实例个数,并行查询可以划分为多机并行查询和单机并行查询。 单机并行查询仅利用了单机的 CPU、磁盘、内存等机器资源,因此又称为本地并行查询。
DM 支持本地并行和多机并行,并行查询具体实现上采用对称并行技术,即每一个执行者(进程或线程)都执行相同的计划。主要执行者负责向其他并行执行者分发计划、搜集数据并向客户返回最终结果。不管是主执行者,还是从执行者,它们执行的计划都是完全相同的。
如果同一条语句执行频率较高,或者每次执行的语句仅仅是常量值不同,则可以考虑使用计划重用机制。避免每次执行都需要优化器进行分析处理,可以直接从计划缓存中获取已有的执行计划,减少了分析优化过程,提高执行率。
对于计划重用,达梦数据库提供了 INI 参数 USE_PLN_POOL 来控制,当置为非 0 时,会启用计划重用。
执行计划的生成与优化是一个非常依赖 CPU 的操作,而执行一个查询获得结果集也是一个非常消耗资源的操作。当系统连续执行两个完全相同的 SQL 语句,其执行计划和结果集很有可能是相同的,如果重新生成和执行计划,会大大浪费系统资源。这时如果使用计划重用和结果集重用,系统的响应速度可以大大提升。
结果集重用是基于计划重用的,如果查询的计划不能缓存,则其查询结果集必然不能缓存。 此外,当语句的游标属性为 FORWARD ONLY 时,默认查询不会生成结果集。而参数BUILD_FORWARD_RS 可以强制在此类查询中生成结果集,以便进行结果集重用。
可通过设置 INI 参数 RS_CAN_CACHE 来控制结果集重用。
当置为 0 时表示手动模式(MANUAL),在此模式下默认不缓存查询结果集,但是 DBA 可以通过语句提示等方法指示系统对必要的查询结果集进行缓存;
当置为 1 时表示强制模式(FORCE),在此模式下默认缓存所有可缓存结果集,但是 DBA 也可以通过新增的配置参数以及语句提示等方法取消某些不合适的结果集缓存。
当 RS_CAN_CACHE 为 1 时,还可以通过设置 INI 参数 RS_CACHE_TABLES 和RS_CACHE_MIN_TIME 对缓存的结果集进行限制和过滤。
DBA 可以通过在 SQL 语句中设置 “RESULT_CACHE”或“NO_RESULT_CACHE” HINT手动指示查询的结果集是否缓存。
select /*+ RESULT_CACHE */ id, name from sysobjects;
或者
select /*+ NO_RESULT_CACHE */ id, name from sysobjects;
在语句中使用 HINT 指定结果集缓存的优先级要高于 INI 中相关参数的设置。
还可以使用系统过程 SP_SET_PLN_RS_CACHE 来强制设置指定计划结果集缓存的生效及失效。 这个系统过程对结果集缓存的指定高于其它所有结果集缓存的设置。
在以下情况下,DM 不支持结果缓存:
(1)必须是单纯的查询语句计划,PL 脚本中包含查询语句也不能缓存结果集。
(2)查询语句的计划本身必须是缓存的。
(3)守护环境中的备库不支持结果集缓存。
(4)MPP 等集群环境下不支持结果集缓存(3、4 两点限制都是因为无法精确控制基表的数据更新时戳)。
(5)查询语句中包含以下任意一项,其结果集都不能缓存:
本文介绍了达梦数据库的查询优化的基本思路、步骤和目标,介绍了查询优化器的处理过程以及查询优化的影响因素,额外介绍了统计信息和执行计划等知识。
如果文中有误,欢迎指出,大家共同交流进步!
更多达梦技术资讯,请访问达梦技术社区:
达梦数据库 - 新一代大型通用关系型数据库 | 达梦云适配中心
https://eco.dameng.com/