查询优化器基础
查询优化是SQL调优的重要理论基础
1. SQL基础简介
1.1 SQL语言特点
- 综合统一
SQL语言集数据定义语言DDL、数据操纵语言DML、数据控制语言DCL的功能于一体。 - 高度非过程化
用户只需提出"做什么",而不必指明"怎么做"。存取路径的选择以及SQL语句的操作过程由系统自动完成。大大减轻了用户负担,而且有利于提高数据独立性。
由于SQL语言高度非过程化的特点,面对一个SQL语句应该要如何处理完全由数据库管理系统决定选择,当处理过程不够高效时就会出现一些性能问题,此时则需要人工的干预,进行性能调优,从而达到我们的性能目标。
1.2 SQL处理过程
SQL语句的处理通常都要经历以下5个阶段:
- 词法语法分析阶段(parser子系统)
检查SQL语句是否符合词法语法规则,然后转化为一颗解析树。 - 语义检查阶段(analyzer子系统)
检查解析树是否符合语义规则,然后转化为一颗查询树。 - 查询重写阶段(rewritter子系统)
如果查询树中涉及视图或者规则,重写为查询树。 - 查询优化阶段(planner子系统)
经过逻辑优化和物理优化,生成最优的执行计划。 - 查询执行阶段(executor子系统)
按照执行计划进行执行。
在SQL处理过程中,优化阶段(planner子系统)选择的执行计划是否最优,会直接影响到执行时间。所以,理解查询优化器的优化过程对SQL调优会有很大帮助,
2. 查询优化器简介
数据库的使用者在书写SQL语句的时候也经常会考虑到查询的性能,根据自己已知的知识争取写出性能很高的SQL语句。但是一个应用程序可能要写大量的SQL语句,而且有些SQL语句的逻辑极为复杂,数据库应用开发人员很难面面俱到地写出性能良好的语句。
因此,查询优化器是提升查询效率非常重要的一个手段,并且查询优化器的优化过程对数据库开发人员是透明的。查询优化器的主要作用就是生成最优的执行计划。对于一条给定的SQL语句,通常会有一个或多个执行计划可供选择,每个执行计划都可以返回正确的结果,优化器会选择预期运行最快的执行计划。
为了给出最优的执行计划,数据库支持以下两种技术:
- 基于规则的 逻辑优化 (RBO)
- 基于代价估算的 物理优化 (CBO) 上述两种技术是自动的,可以极大地解放数据库应用开发人员的生产力。反过来,理解这两种优化手段也有助于使用者开发出更高效的SQL语句,在优化SQL语句性能的时候选择更加适合的优化手段。
3. 逻辑优化
查询优化器在逻辑优化阶段的主要任务是, 根据关系代数的等价变换规则找出更高效的变换形式。(有关关系代数的等价变换原理可以参照 《数据库系统实现》 )。
3.1 逻辑优化规则
- 视图重写 视图重写是将对视图的引用重写为对基表的引用,数据库把带有视图的查询转换为基于表的子查询,因此重写后视图作为子查询进行进一步优化。数据库在查询重写阶段进行视图重写优化。
- 表达式预处理
这一阶段主要进行常量化简、连接溯源、表达式规范化等操作。 - 子查询优化
应用子查询优化技术,上拉子链接((NOT) EXISTS / IN / ANY…)、上拉子查询,使得子查询和父查询处于同一层次,作为连接关系与外层父查询并列,也就是将子查询重写为等价的多表连接,从而尽可能减少查询语句的层次,便于物理优化阶段考虑多种不同的连接方式。 - 等价谓词重写
数据库执行引擎对一些谓词处理的效率要高于其他谓词,基于这点,把逻辑表达式重写成等价的且效率更高的形式,能有效提高查询执行效率。 - 条件化简
WHERE、HAVING和ON条件由许多表达式组成,而这些表达式在某些时候彼此之间存在一定的联系。利用等式和不等式的性质,可以将WHERE、HAVING和ON条件化简 - 外连接消除 查询优化将满足特定条件的外连接转换为内连接,完成外连接的消除,转换的条件是看WHERE子句中与内表相关的条件是否满足"空值拒绝条件"(reject-NULL条件)。
- 谓词下推
"选择"、"投影"操作下推(WHERE/JOIN-ON中的条件尽可能下推),使得关系在连接前尽量减少元组数量,从而减少连接后得到的中间关系的大小。 - 连接消除
主外键参照的表进行外连接,可以消除主键表,从而消除连接。 - 合并子查询的公共表达式
将SQL语句中的子查询的公共表达式进行合并,只需执行一次获得所需结果集,后续直接引用该结果集即可,减少对公共表达式的重复计算。
3.2 逻辑操作符简介
构造查询的基本动作通常由关系代数的操作符来表达,常见关系代数的操作符包括:
- 选择: 与SQL查询的WHERE子句对应
- 投影: 与SQL查询的SELECT子句对应
- 卡氏积: 与SQL查询的FROM子句的关系列表对应
- 连接: 与SQL查询中的JOIN、NATURAL JOIN和OUTER JOIN对应,并、交和差:与SQL的操作符UNION、INTERSECT、EXCEPT相对应
- 消除重复: 与SQL SELECT子句中的关键字DISTINCT对应,SQL的操作符UNION、INTERSECT、EXCEPT默认消除重复
- 分组: 与SQL查询中的GROUP BY对应
- 排序: 与SQL查询中的ORDER BY具有相同的效果
下面用一个查询来举例说明基础的逻辑操作符:
SELECT title, birthdateFROM MovieStar, StarsInWHERE year = 1996 AND
gender = ‘F’ AND starName = name;
4. 物理优化
4.1 物理优化简介
物理优化则是基于代价的查询优化(Cost-based Optimizer,简称CBO),其主要流程是枚举各种待选的物理查询路径,并且根据路径上各节点的信息计算这些待选路径的代价,进而选择出代价最小的路径。
物理路径多样的原因在于,对于各种关系代数操作符(选择、排序、连接、聚集等),执行器中存在着一个或者多个节点(算法)来应对不同情况。
- 选择: seqscan、indexscan、index only scan等
- 排序: sort、indexscan。
- 连接: hashjoin、merge join、nestloopjoin。(连接顺序也可以变化)
- 聚集: hashaggregate、groupaggregate。
提示
对于一个形如select from t1,t2 where t1.c1 = t2.c1 and t1.c3 < 100
的语句,选择操作符有seqscan、indexscan、indexonly、scan四种节点(实际情况不止,还有其他扫描节点)可选; t1和t2的连接操作符有hashjoin、merge join、nestloop join三种节点可选(实际情况不止, join算法存在变种)。该语句则存在着 3*3=9 种物理路径可选。
4.2 代价估算模型
为了合理高效的评估诸多路径中哪条路径最优,我们需要构建一个可量化的模型,也就是代价估算模型,以此来衡量各条路径的代价。
- 代价估算 代价估算是估算某个路径(可以是单表的路径,也可以是两个关系连接得到的路径,还可以是多个表根据不同的连接方式得到的连接路径)的花费,属于物理优化范畴。 这些路径中每个节点主要的操作可归纳为数据的读写及运算,这些操作所产生的开销主要为CPU开销及I/O开销,某些情况下会有通信代价(如并行)。所以查询优化代价估算基于CPU开销和IO开销,即:
总代价 =I/O代价 + CPU代价+ 通信代价(并行)
大体上来说,I/O和CPU的代价估算和需要进行IO和CPU运算的数据规模相关,而数据规模的估算和选择率则关系密切。 - 选择率计算 选择率表示"过滤条件"或者"连接条件"在执行完成后剩余的元组数和总行数的比例,选择率的不同会直接影响到算法的选择。为了快速的给出代价评估,选择率的估算会依赖于统计信息,使得优化器可以在不真正执行语句的情况下就给出代价估算。
例如:
create table t_sel(id int, name text);
insert into t_sel values(generate_series(1,10000), random());
select count(*) from t_sel;
COUNT
-------
10000
(1 row)
analyze t_sel;
explain select * from t_sel where id < 50;
QUERY PLAN
---------------------------------------------------------
Seq Scan on T_SEL (cost=0.00..189.00 rows=49 width=21)
Filter: (ID < 50)
(2 rows)
在本例中,优化器会根据统计信息得到以下信息:
查出t_sel表的总行数为10000
计算出条件id <50的选择率为0.49%
进而可以计算出经过id < 50过滤条件后的行数为49。
使用统计信息来估计选择率,需要及时的收集采样信息以确保统计信息能够尽量真实的反应数据分布情况,否则选择率的计算可能产生较大的偏差,进而影响最优执行计划的生成。
4.3 单表扫描代价估算
在对数据做任何处理之前,通常都需要获取单表的数据,KingbaseES中常见的单表扫描方式包括:
- 顺序扫描(SeqScan)
- 索引扫描(IndexScan)
- 顺序扫描
顺序扫描也叫全表扫描,基本算法为:根据实际的数据的存取顺序,连续扫描表中的所有数据。适用于:选择率高的情况
在选择率高到一定程度的时候,相比于顺序扫描,索引扫描多出扫描索引的代价,所以付出的代价会超过顺序扫描。
顺序扫描代价估计:
顺序扫描操作的代价估算:tuple IO代价+tuple CPU扫描代价。
页面数和tuple数来源于统计信息。
例如:
-- 数据准备
create table scan (a int);
insert into scan values (generate_series(1,100000));
create index ON scan(a);
analyze scan;
-- 高选择率情况: 选择率高时,优化器自动选择seqscan作为扫描的算法。
explain analyze select * from scan where a>1;--选择率为0.99999
QUERY PLAN
-----------------------------------------------------------------------------------------
Seq Scan on SCAN (cost=0.00..1693.00 rows=100000 width=8) (actual time=0.025..38.464 rows=99999 loops=1)
Filter: (A > 1)
Rows Removed by Filter: 1
Planning time: 0.244 ms
Execution time: 49.086 ms
(5 rows)
-- 通过禁用seqscan让优化器使用indexscan
set enable_seqscan to off;
explain analyze select * from scan where a>1;
QUERY PLAN
-----------------------------------------------------------------------------------------
Index Scan using SCAN_A_IDX on SCAN (cost=0.29..3300.29 rows=100000 width=8) (actual time=0.031..47.528 rows=99999 loops=1)
Index Cond: (A > 1)
Planning time: 0.249 ms
Execution time: 58.148 ms
(4 rows)
通过上面可以看出当选择率高的时候:
使用seqscan的实际执行总代价(实际花费38.464ms)要小于使用索引扫描的实际执行总代价(实际花费47.528ms)。在上述例子中,选择率接近1,全表扫描和索引扫描都要读取所有的页面和tuples,除此之外,索引扫描还需要读取索引页面,所以索引扫描比全表扫描慢。
- 索引扫描
索引扫描的基本算法为,根据条件值,通过索引结构快速的找到条件值的索引位置,进而找出所有符合条件的值。适用于:选择率低的情况
在选择率低的情况下,索引扫描需要扫描的索引页面和数据页面要小于顺序扫描,由此付出的IO和CPU代价要小于顺序扫描,所以优化器会选择索引扫描。
索引扫描代价估计:
索引扫描操作的代价估算:索引扫描代价+tuple IO代价+tuple CPU扫描代价。
例如:
-- 低选择率情况: 优化器自动选择Indexscan的计划
explain analyze select * from scan where a=10;--选择率为0.000001
QUERY PLAN
-----------------------------------------------------------------------------------------
Index Scan using SCAN_A_IDX on SCAN (cost=0.29..8.31 rows=1 width=8)
Index Cond: (A = 10)
Planning time: 0.226 ms
Execution time: 0.073 ms
(4 rows)
-- 禁用indexscan和bitmapscan,使用seqscan的计划
set enable_indexscan = off;
set enable_bitmapscan = off;
explain analyze select * from scan where a=10;
QUERY PLAN
-----------------------------------------------------------------------------------------
Seq Scan on SCAN (cost=0.00..1693.00 rows=1 width=8) (actual time=0.037..22.824 rows=1 loops=1)
Filter: (A = 10)
Rows Removed by Filter: 99999
Planning time: 0.115 ms
Execution time: 22.858 ms
(5 rows)
通过上面可以看出当选择率低的时候:
使用seqscan实际执行总代价(实际花费22.824ms)大于索引扫描实际执行总代价(实际花费8.31ms)。在上述例子中,选择率接近0,全表扫描要扫所有的页面和tuples,而索引扫描只需要扫索引页面和从一个页面读一个tuple,因此索引扫描比较快。
4.4 两表连接代价估算
在数据库中表与表的连接为一项重要的操作,两表连接作为连接的基础尤其重要。对于DBA而言,调整连接方式是SQL调优的常用手段。研究两表连接的方式,选择合适的连接方式,对SQL执行效率的提高有着直接的帮助。
为了支撑复杂多变的SQL语句,数据库提供了3种类型的连接方式。每种类型的连接方式对应不同的算法实现,本文将一一介绍三种连接类型的算法原理及适用场景。
- 嵌套循环连接(NestLoop) 嵌套循环连接为两表连接最基本的算法。
a. 基本嵌套循环连接循环嵌套连接算法本质为一个两层循环,循环外层的表每次拿出一条元组与循环内层的表作比较逐一匹配是否满足连接条件选出符合连接条件的元组。 适用于:1)内外表数据量不大的情况 2)内表数据量很小,外表数据量大的情况 嵌套循环连接代价估计 通常情况下由于连接操作主要花费CPU资源。所以从理论的角度分析,嵌套循环连接操作的代价估算: 扫描外表的代价和扫描内表的代价之和 例如: 存在两张表T1与T2做循环嵌套连接,T1作为循环外层的表存在的元组条数为n,T2存在的元组条数为m。
-- 准备数据
create table t1(a int);
create table t2(a int);
insert into T1 values (generate_series(1,1000000));
insert into T2 values (generate_series(1,1000000));
-- 循环嵌套连接时。表T1将每次拿出一条元组,与T2的m条元组逐一比较。此时在连接中T1表循环了1次、T2表循环了n次,共比较了n*m次。
explain analyze select * from T1 join T2 on t1.a=t2.a where t1.a<10 and t2.a=1;
QUERY PLAN
-----------------------------------------------------------------------------------------
Nested Loop (cost=0.00..36350.01 rows=1 width=8) (actual time=0.057..286.687 rows=1 loops=1)
-> Seq Scan on T1 (cost=0.00..19425.00 rows=1 width=4) (actual time=0.034..116.766 rows=1 loops=1)
Filter: ((A < 10) AND (A = 1))
Rows Removed by Filter: 999999
->Seq Scan on T2 (cost=0.00..16925.00 rows=1 width=4) (actual time=0.018..169.916 rows=1 loops=1)
Filter: (A = 1)
Rows Removed by Filter: 999999
Planning time: 0.190 ms
Execution time: 286.740 ms
b. 物化嵌套循环连接
每当读取外部表的每个元组时,上述嵌套循环连接都必须扫描内部表的所有元组。由于为每个外部表元组对内部表做全表扫描是一个昂贵的过程,因此KingbaseES支持物化嵌套循环连接以减少内部表的全表扫描成本。 在运行嵌套循环连接之前,执行程序通过使用临时元组存储模块对内部表进行一次扫描,将内部表元组写入work_mem或临时文件中。与使用缓冲区管理器相比,它有可能更有效地处理内部表元组,尤其是当所有元组都能写入work_mem中时。 适用于:内表数据被重复访问
例如:
--物化循环嵌套连接时T2的结果物化在内存中。表T1将每次拿出一条元组,与T2的m条元组逐一比较。此时在连接中T1表循环了1次、T2表循环了n次,共比较了n*m次。
explain analyze select * from T1 join T2 on t1.a=t2.a where t1.a<100 and t2.a<100;--使用where条件控制表T1、T2数据量 |
QUERY PLAN
-----------------------------------------------------------------------------------------
Nested Loop (cost=0.00..34000.25 rows=1 width=8) (actual time=0.091..289.167 rows=99 loops=1)
Join Filter: (T1.A = T2.A)
Rows Removed by Join Filter: 9702
-> Seq Scan on T1 (cost=0.00..16925.00 rows=100 width=4) (actual time=0.046..115.298 rows=99 loops=1)
Filter: (A < 100)
Rows Removed by Filter: 999901
-> Materialize (cost=0.00..16925.50 rows=100 width=4) (actual time=0.000..1.741rows=99 loops=99)
-> Seq Scan on T2 (cost=0.00..16925.00 rows=100 width=4) (actual time=0.037..171.538 rows=99 loops=1)
Filter: (A < 100)
Rows Removed by Filter: 999901
Planning time: 0.215 ms
Execution time: 289.226 ms
(12 rows)
c. 索引嵌套循环连接 如果存在内部表的索引,并且该索引可以用于满足联接条件的元组以匹配外部表的每个元组,那么优化器会考虑使用此索引直接搜索内部表元组,而不是顺序扫描。这种变化称为索引嵌套循环联接;尽管该算法引用了索引"嵌套循环连接",但该算法基本上只需要在外部表上循环一次,因此,它可以有效地执行连接操作。 适用于:外部表数据量不大,内部表可以使用索引扫描一次很快的情况 索引嵌套循环连接代价:扫描外表代价+使用索引扫描内表代价(通常小于全表扫描内表代价)
例如:
-- 索引循环嵌套连接时T2表使用索引提高扫描速度。表T1将每次拿出一条元组,与T2的m条元组逐一比较。此时在连接中T1表循环了1次、T2表循环了n次,共比较了n*m次。
create index on t2(a);
explain analyze select * from T1 join T2 on t1.a=t2.a where t1.a<100 and t2.a<10;
QUERY PLAN
--------------------- --------------------------------------------------------------------
Nested Loop (cost=0.42..16947.10 rows=1 width=8) (actual time=0.049..171.314 rows=9 loops=1)
Join Filter: (T1.A = T2.A)
Rows Removed by Join Filter: 882
-> Seq Scan on T1 (cost=0.00..16925.00 rows=100 width=4) (actual time=0.032..170.853 rows=99 loops=1)
Filter: (A < 100)
Rows Removed by Filter: 999901
-> Materialize (cost=0.42..8.63 rows=9 width=4) (actual time=0.000..0.002 rows=9 loops=99)
-> Index Only Scan using T2_A_IDX on T2 (cost=0.42..8.58 rows=9 width=4) (actual time=0.007..0.013 rows=9 loops=1)
Index Cond: (A < 10)
Heap Fetches: 9
Planning time: 0.397 ms
Execution time: 171.371 ms
(12 rows)
4.5 归并连接(MergeJoin)
与嵌套循环连接不同,归并联接只能用于自然联接和等值联接。
- 基本归并连接
归并连接的基本原理为对两个表的数据进行排序,然后做连接。归并连接算法的步骤是:
- 将每个表排好序
- 读入每个表的元组
- 找出两个表中最小的元组进行匹配
- 找出次小的元组匹配依此类推,完成其他元组的两表连接。
归并连接算法要求内外表都是有序的,所以对于内外表都要排序。但是如果内表或者外表有索引,就不需要进行排序,只对无索引的表进行排序。如果两表都有索引,则两表都不需要排序。
适用情况:1)两个表的数据都是基本有序 2)两表可以使用索引或者外表有索引
归并连接代价估计:
通常情况下由于连接操作主要花费CPU资源。所以从理论的角度分析,归并连接操作的代价估算: 扫描外表代价+扫描内表代价+外表排序代价+内表排序代价。
例如:
create table t1(a int);
create table t2(a int);
insert into T1 values (generate_series(1,1000000));
insert into T2 values (generate_series(1,1000000));
analyze ;
explain analyze select * from T1 join T2 on t1.a=t2.a where t1.a<100 and t2.a<10 order by t1.a;--使用order by让表t1有序
QUERY PLAN
-----------------------------------------------------------------------------------------
Merge Join (cost=33856.66..33857.67 rows=1 width=12) (actual time=276.471..276.478 rows=10 loops=1)
Merge Cond: (T1.A = T2.A)
-> Sort (cost=16928.32..16928.57 rows=100 width=4) (actual time=164.937..164.938 rows=10 loops=1)
Sort Key: T1.A
Sort Method: quicksort Memory: 29kB
-> Seq Scan on T1 (cost=0.00..16925.00 rows=100 width=4) (actual time=0.036..164.876 rows=99 loops=1)
Filter: (A < 100)
Rows Removed by Filter: 999901
-> Sort (cost=16928.33..16928.58 rows=100 width=4) (actual time=111.522..111.524 rows=10 loops=1)
Sort Key: T2.A
Sort Method: quicksort Memory: 25kB
-> Seq Scan on T2 (cost=0.00..16925.01 rows=100 width=4) (actual time=0.021..111.506 rows=10 loops=1)
Filter: (A < 10)
Rows Removed by Filter: 999991
Planning time: 1.085 ms
Execution time: 276.591 ms
(16 rows)
- 物化归并连接 与嵌套循环连接类似,归并连接支持物化归并连接(Materialized Merge Join),物化内表,使内表扫描更为高效。其原理图如下:
例如:
explain analyze select * from T1 join T2 on t1.a=t2.a;
QUERY PLAN
-----------------------------------------------------------------------------------------
Merge Join (cost=255514.69..278014.69 rows=1000000 width=8) (actual time=380.418..910.112 rows=1000000 loops=1)
Merge Cond: (t1.a = t2.a)
-> Sort (cost=127757.34..130257.34 rows=1000000 width=4) (actual time=198.506..282.071 rows=1000000 loops=1)
Sort Key: t1.a
Sort Method: external merge Disk: 13736kB
-> Seq Scan on t1 (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.028..68.415 rows=1000000 loops=1)
-> Materialize (cost=127757.34..132757.34 rows=1000000 width=4)(actual time=181.903..360.212 rows=1000000 loops=1)
-> Sort (cost=127757.34..130257.34 rows=1000000 width=4) (actual time=181.900..267.644 rows=1000000 loops=1)
Sort Key: t2.a
Sort Method: external merge Disk: 13736kB
-> Seq Scan on t2 (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.010..62.750 rows=1000000 loops=1)
Planning Time: 0.143 ms
Execution Time: 938.136 ms
(13 行记录)
4.6 Hash连接(HashJoin)
与归并连接相同同,hash联接只能用于自然联接和等值联接。 内存中的Hash连接是在work_mem中处理的。Hash连接有两个阶段:构建阶段和探测阶段。在构建阶段,内表中的所有元组都会被插入到桶中;在探测阶段,每条外表的元组都会与处理桶中的内表元组比较,如果满足条件则将两条元组连接起来。
在上面的Hash连接步骤中,步骤(1)至(3)是Hash连接的构建阶段,步骤(4)至(5)是探测阶段。
Hash连接的基本原理为:
- 对内表建立hash表,扫描所有内表数据到各个hash桶里面。
- 一行行扫描外表数据,对外表数据进行hash,hash到某个桶里面。
- 跟这个桶里面的数据进行连接。
适用情况:数据分布比较随机无序,重复值不是特别多的情况。
Hash连接代价估计:
通常情况下由于连接操作主要花费CPU资源。所以从理论的角度分析,Hash连接操作的代价估算: 内表创建hash代价+(n_outer_tuple × m_inner_tuple × 选择率)× a_tuple_cpu_time 其中a_tuple_cpu_time获取一个元组消耗的CPU时间。n_outer_tuple,扫描获取的外表元组数。m_inner_tuple,扫描获取的内表元组数。
例如:
create table t1(a int);
create table t2(a int);
insert into T1 values (generate_series(1,1000000));
insert into T2 values (generate_series(1,1000000));
analyze ;
explain analyze select * from T1 join T2 on t1.a=t2.a where t1.a<1000 and t2.a<100 ;--与前文nestloop相比增大了数据量
QUERY PLAN
-----------------------------------------------------------------------------------------
Hash Join (cost=16926.26..33855.03 rows=1 width=8) (actual time=174.828..285.586 rows=100 loops=1)
Hash Cond: (T1.A = T2.A)
-> Seq Scan on T1 (cost=0.00..16925.00 rows=1002 width=4) (actual time=0.035..110.639 rows=999 loops=1)
Filter: (A < 1000)
Rows Removed by Filter: 999001
-> Hash (cost=16925.01..16925.01 rows=100 width=4) (actual time=174.772..174.772 rows=100 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 12kB
-> Seq Scan on T2 (cost=0.00..16925.01 rows=100 width=4) (actual time=0.019..174.723 rows=100 loops=1)
Filter: (A < 100)
Rows Removed by Filter: 999901
Planning time: 0.263 ms
Execution time: 285.650 ms
5. 优化器的局限性
数据库的查询优化器在不断的提升自己的优化能力,但是总会有无法覆盖的情况。对于代价计算不准(多条件、多表连接、相关子查询条件等)的情况,建议用户通过HINT的方式人工指定计划。
6. 优化器统计信息
6.1 统计信息简介
数据库优化器支持基于成本的物理优化(CBO),而物理优化的核心是代价估算。在不真正执行语句之前,提前对代价给出较为合理的估算则是优化器的一项必要的能力。为了支持代价估算,数据库提供了统计信息,以采样的方式收集信息,来反应表和索引数据的概览情况,方便优化器在短时间内对代价进行评估。
6.2 统计信息内容
以单表扫描为例,代价估算主要依赖于以下信息,这也是统计信息需要收集的主要内容:
- 表级统计信息:页面数、元组数
- 列级统计信息:直方图、MVC等
列级统计信息主要反应数据的分布情况,进而帮助计算选择率。 单表扫描操作的代价估算公式如下所示:
顺序扫描 N_page * a_tuple_IO_time + N_tuple * a_tuple_CPU_time
索引扫描 C_index + N_page_index * a_tuple_IO_time
说明如下:
N_page,数据页面数。
a_page_IO_time,一个页面的IO代价。
N_page_index,索引页面数。
a_tuple_CPU_time,一个元组从页面中解析的CPU代价。
N_tuple,元组数。
C_index,索引的IO代价,C_index = N_page_index×a_page_IO_time。
N_tuple_index,索引作用下的可用元组数,N_tuple_index = N_tuple×索引选择率。
- 表级信息
表和索引是数据库里最基本的对象,它们的基本信息对优化器来说也是最重要的。优化器需要知道每一个表和索引占用的磁盘块数,以及表和索引包含的记录数。这些信息主要保存在sys_class表中:
select * from sys_class where relname = 'student';
-[ RECORD 1 ]-------+--------
oid | 16408
relname | student
…
relpages | 64
reltuples | 10000