SQL查询优化器浅析(三) | 青训营笔记

218 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第3天

常见的查询优化器——介绍查询优化器的分类,重点介绍RBO和CBO的原理

Oracle的优化器有两种优化方式,即基于规则的优化方式(Rule-Based Optimization,简称为RBO)和基于代价的优化方式(Cost-Based Optimization,简称为CBO),在Oracle8及以后的版本,Oracle强列推荐用CBO的方式

RBO方式:优化器在分析SQL语句时,所遵循的是Oracle内部预定的一些规则。比如我们常见的,当一个where子句中的一列有索引时去走索引。

CBO方式:它是看语句的代价(Cost),这里的代价主要指Cpu和内存。优化器在判断是否用这种方式时,主要参照的是表及索引的统计信息。统计信息给出表的大小、有少行、每行的长度等信息。这些统计信息起初在库内是没有的,是做analyze后才出现的,很多的时侯过期统计信息会令优化器做出一个错误的执行计划,因些应及时更新这些信息。

注意:走索引不一定就是优的,比如一个表只有两行数据,一次IO就可以完成全表的检索,而此时走索引时则需要两次IO,这时全表扫描(full table scan)是最好

优化模式包括Rule、Choose、First rows、All rows四种方式:

Rule:基于规则的方式。

Choolse:默认的情况下Oracle用的便是这种方式。指的是当一个表或或索引有统计信息,则走CBO的方式,如果表或索引没统计信息,表又不是特别的小,而且相应的列有索引时,那么就走索引,走RBO的方式。

First Rows:它与Choose方式是类似的,所不同的是当一个表有统计信息时,它将是以最快的方式返回查询的最先的几行,从总体上减少了响应时间。

All Rows:也就是我们所说的Cost的方式,当一个表有统计信息时,它将以最快的方式返回表的所有的行,从总体上提高查询的吞吐量。没有统计信息则走RBO的方式。

RBO

优化原则、列裁剪、谓词下推、传递闭包、Runtime Filter

Read data less and faster(I/O)

Transfer data less and faster(Network)

Process data less and faster(CPU&Memory)

SELECT pv.siteld,user.name
FROM pv JOIN user
ON pv.siteld = user.siteld AND pv.userld = user.id
WHERE user.siteld > 123;

主流RBO实现一般都有几百条基于经验归纳得到的优化规则

优点:实现简单,优化速度快

缺点:不保证得到最优的执行计划

单表扫描:索引扫描(随机I/O) vs 全表扫描(顺序I/O)

如果查询的数据分布非常不均衡,索引扫描可能不如全表扫描

Join的实现:Hash Join vs SortMerge Join
两表Hash Join:用小表构建哈希表——如何识别小表?
多表Join:
哪种连接顺序是最优的?
是否要对每种组合都探索?

N个表连接,仅仅是left-deep tree就有差不多N!种连接顺序

e.d.N = 10 -> 总共3628800个连接顺序

CBO

使用一个模型估算执行计划的代价,选择代价最小的执行计划

执行计划的代价等于所有算子的执行代价之和

通过RBO得到(所有)可能的等价执行计划

算子代价:CPU、内存,磁盘I/O,网络I/O等代价

和算子输入数据的统计信息有关:输入、输出结果的行数、每行大小...

叶子算子Scan:通过统计原始数据得到

中间算子:根据一定的推导规则,从下层算子的统计信息推到得到

和具体的算子类型,以及算子的物理实验有关

例子:Spark Join 算子代价 = weight * row_count + (1.0 - weight) * size

统计信息 + 推导规则 —> 计算算子代价 —> 计算执行计划代价 —> 执行计划枚举

统计信息

原始表统计信息

表或者分区级别:行数、行平均大小、表在磁盘中占用了多少字节等

列级别:min、max、num nulls、num not nulls、num distinct value(NDV)、histogram等

推导统计信息

选择率(selectivity):对于某一个过滤条件,查询会从表中返回多大比例的数据

基数(cardinality):在查询计划中常指算子需要处理的行数

准确的cardinality,远比代价模型本身重要

统计信息的收集方式

在DDL里指定需要收集的统计信息,数据库会在数据写入时收集或者更新统计信息

CREATE TABLE REGION(
    R_REGIONKEY INT NOT NULL,
    R_NAME CHAR(25) NOT NULL,
    R_COMMENT VARCHAR(152)
)DUPLICATE KEY(R_REGIONKEY)
DISTRIBUTED BY HASH(R_REGIONKEY) BUCKETS 1
PROPERTIES("stats_columns" = "R_NAME");

手动执行explain analyze statement,触发数据库收集或者更新统计信息

ANALYZE TABLE table_name COMPUTE STATISTICS FOR COLUMNS column-name1,column-name2,...;

动态采样

SELECT count(*) FROM table_name;