查询优化器在逻辑优化阶段主要解决的问题是:如何找出SQL语句等价的变换形式,使得SQL执行更高效。
用于优化的思路包括:
各种逻辑优化技术依据关系代数和启发式规则进行。
查询优化技术的理论基础是关系代数。
关系数据库基于关系代数。关系数据库的对外接口是SQL语句,所以SQL语句中的DML、DQL基于关系代数实现了关系的运算。
作为数据库查询语言的基础,关系模型由关系数据结构、关系操作集合和关系完整性约束三部分组成。与关系模型有关的概念:
关系代数的运算符类别包括:
基本关系运算与对应的SQL表(表2-1):
各种连接运算的语义表(表2-2):
关系代数表达式的等价,就是相同的关系代替两个表达式中相应的关系,所得到的结果是相同的。两个关系表达式El和E2是等价的,记为E1≡E2。
查询语句可以表示为一棵二叉树,其中:
最后两项,主要依据重写规则和物理查询优化中涉及的技术。
不同运算符根据其特点,可以对查询语句做不同的优化。但优化的前提是:优化前和优化后的语义必须等价。
运算符主导的优化(表2-3):
选择下推到集合的运算(表2-4):
投影下推到集合的运算(表2-5):
经过等价变换优化带来的好处,再加上避免了原始方式引入的坏处,使得查询效率明显获得提升。
因为运算符中考虑的子类型(见表2-3中的“子类型”列),实则是部分考虑了运算符间的关系、运算符和操作数间的关系,其本质是运算规则在起作用。所以前节考虑过关系代数运算规则对优化的作用,但不完整,这里补充余下的对优化有作用的主要关系代数运算规则。
运算规则主导的优化(表2-6):
传统的联机事务处理(OLTP)使用基于选择(SELECT)、投影(PROJECT)、连接(JOIN)3种基本操作相结合的查询,称为SPJ查询。对这3种基本操作优化的方式如下:
根据SQL语句的形式特点,还可以做如下区分:
针对SPJ和非SPJ的查询优化,其实是对以上多种操作的优化。“选择”和“投影”操作,可以在关系代数规则的指导下进行优化。表连接,需要多表连接的相关算法完成优化。其他操作的优化多是基于索引和代价估算完成的。
子查询在查询语句中经常出现,是比较耗时的操作。优化子查询对查询效率的提升有着直接的影响,所以子查询优化技术,是数据库查询优化引擎的重要研究内容。
子查询出现在SQL语句的位置和对优化的影响:
按子查询中的关系对象与外层关系对象间的关系分类:
SELECT * FROM t1 WHERE col_1 = ANY(SELECT col_1 FROM t2 WHERE t2.col_2 = t1.col_2); /*子查询语句中存在父查询的t1表的col_2列*/
SELECT * FROM t1 WHERE col_1 = ANY(SELECT col_1 FROM t2 WHERE t2.col_2 = 10); /*子查询语句中(t2)不存在父查询(t1)的属性*/
按特定谓词分类:
按语句的构成复杂程度分类:
按结果集的角度分类:
(1)做子查询优化的原因
在数据库实现早期,查询优化器对子查询一般采用嵌套执行的方式,即对父查询中的每一行,都执行一次子查询,这样子查询会执行很多次,效率很低。
对子查询进行优化,可能带来几个数量级的查询效率的提高。子查询转变成为连接操作之后,有如下好处:
(2)子查询优化技术
子查询优化技术的思路如下:
SELECT * FROM t1 WHERE a1<10 AND (EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=1) OR EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=2));
-- 可优化为:
SELECT * FROM t1 WHERE a1<10 AND (EXISTS (SELECT a2 FROM t2 WHERE t2.a2<5 AND (t2.b2=1 OR t2.b2=2) /*大两个EXISTS子句合并为一个,条件也进行了合并*/
SELECT * FROM t1, (SELECT * FROM t2 WHERE t2.a2>10) v_t2 WHERE t1.a1<10 AND v_t2.a2<20;
--可优化为:
SELECT * FROM t1, t2 WHERE t1.a1<10 AND t2.a2<20 AND t2.a2> 10;/*子查询变为了t1、t2表的连接操作,相当于把t2表从子查询中上拉了一层*/
SELECT * FROM t1 WHERE t1.a1 > (SELECT avg(t2.a2) FROM t2);
(3)子查询展开
子查询展开是一种最为常用的子查询优化技术,子查询展开有以下两种形式:
把子查询上拉到上层,前提是上拉(展开)后的结果不能带来多余的元组,所以需要遵循如下规则:
子查询展开的具体步骤如下:
1)将子查询和上层查询的FROM子句连接为同一个FROM子句,并且修改相应的运行参数。
2)将子查询的谓词符号进行相应修改(如:IN修改为=ANY)。
3)将子查询的WHERE条件作为一个整体与上层查询的WHERE条件合并,并用AND条件连接词连接,从而保证新生成的谓词与原谓词的上下文意思相同,且成为一个整体。
子查询的格式有多种,常见的子查询格式有IN类型、ALL/ANY/SOME类型、EXISTS类型。
(1)IN类型
IN类型有3种不同的格式:
-- 格式一:
outer_expr [NOT] IN (SELECT inner_expr FROM ...WHERE subquery_where)
-- 格式二:
outer_expr = ANY (SELECT inner_expr FROM ... WHERE subquery_where)
--- 格式三:
(oe_1,oe_N) [NOT] IN (SELECT ie_1, ie_N FROM ... WHERE subquery_where)
IN类型子查询优化的情况表(表2-7):
情况一:outer_expr和inner_expr均为非NULL值。
优化后的表达式(外部条件outer_expr下推到子查询中):
EXISTS (SELECT 1 FROM ... WHERE subquery.where AND outer_expr=inner_expr)
-- 即:
EXISTS (SELECT 1 FROM ... WHERE subquery_where AND oe_1 = ie_1 AND ... AND oe_N = ie_N)
子查询优化需要全部满足两个条件:
情况二:outer_expr是非NULL值(情况一的两个转换条件中至少有一个不满足时)。
优化后的表达式(外部条件outer_expr下推到子查询中,另外内部条件inner_expr为NULL):
EXISTS (SELECT 1 FROM ... WHERE subquery.where AND (outer_expr = inner_expr OR inner_expr IS NULL))
假设outer_expr是非NULL值,但是如果outer_expr=inner_expr表达式不产生数据,则outer_expr IN (SELECT ...)的计算结果有如下情况:
情况三:outer_expr为NULL值。
原先的表达式等价于:
NULL IN (SELECT inner.expr FROM ... WHERE subquery_where)
假设outer_expr是NULL值,NULL IN(SELECTinner_expr...)的计算结果有如下情况:
还有需要说明的是:
(2)ALL/ANY/SOME类型
ALL/ANY/SOME类型的子查询格式:
outer_expr operator ANY (subquery) outer_expr operator SOME (subquery) outer_expr operator ALL (subquery)
使用这类子查询需要注意:
如果子查询中没有GROUPBY子句和聚集函数,则下面的表达式还可以使用聚集函数MAX/MIN做类似下面的等价转换:
(3)EXISTS类型
EXISTS类型的子查询格式:
[NOT] EXISTS (subquery)
需要注意几点:
视图重写就是将对视图的引用重写为对基本表的引用。视图重写后的SQL多被作为子查询进行进一步优化。所有的视图都可以被子查询替换,但不是所有的子查询都可以用视图替换。
从视图的构成形式可以分为:
示例:
-- 创建表和视图
CREATE TABLE t_a(a INT, b INT);
CREATE VIEW v_a AS SELECT * FROM t_a;
-- 视图的查询命令
SELECT col_a FROM v_a WHERE col_b>100;
-- 视图重写后的形式
SELECT col_a FROM (
SELECT col_a, col_b FROM t_a
) WHERE col_b > 100;
-- 优化后的等价形式:
SELECT col_a FROM t_a WHERE col_b > 100;
简单视图能被较好地优化;但是复杂视图则不能被很好的优化器。像Oracle等商业数据库,提供了一些视图的优化技术,如“复杂视图合并”、“物化视图查询重写”等。但复杂视图优化技术还有待提高。
数据库执行引擎对一些谓词处理的效率要高些,因此把逻辑表达式重写成等价的且效率更高的形式,能有效提高查询执行效率。这就是等价谓词重写。
1. LIKE规则
改写LIKE谓词为其他等价的谓词,以更好地利用索引进行优化。如:
name LIKE 'Abc%'
-- 重写为
name >= 'Abc' AND name < 'Abd'
应用LIKE规则的好处是:转换前只能进行全表扫描,如果目标列上存在索引,则转换后可以进行索引范围扫描。
LIKE匹配的表达式中,若没有通配符(%或_),则与=等价。如:
name LIKE 'Abc'
-- 重写为
name = 'Abc'
2. BETWEEN-AND规则
改写BETWEEN-AND谓词为其他等价的谓词,以更好地利用索引进行优化。与LIKE谓词的等价重写类似,如:
sno BETWEEN 10 AND 20
-- 重写为
sno >= 10 AND sno <= 20
应用BETWEEN-AND规则的好处是:如果sno上建立了索引,则可以用索引扫描代替原来BETWEEN-AND谓词限定的全表扫描,从而提高了查询的效率。
http://3.IN转换OR规则
这里是指IN操作符操作,不是IN子查询。改写IN谓词为等价的OR谓词,以更好地利用索引进行优化。如:
age IN (8,12,21)
-- 重写为
age = 8 OR age = 12 OR age = 21
如果数据库对IN谓词只支持全表扫描且OR谓词中表的age列上存在索引,则转换后查询效率会提高。
http://4.IN转换ANY规则
改写IN谓词为等价的ANY谓词。因为IN可以转换为OR,OR可以转为ANY,所以可以直接把IN转换为ANY。如
age IN (8,12,21)
-- 重写为
age ANY (8,12,21)
效率是否能够提高,依赖于数据库对于ANY操作的支持情况。如PostgreSQL没有显式支持ANY操作,但是在内部实现时把IN操作转换为了ANY操作。
5.OR转换ANY规则
改写OR谓词为等价的ANY谓词,以更好地利用MIN/MAX操作进行优化。如:
sal>1000 OR dno=3 AND (sal>1100 OR sal>base_sal+100) OR sal>base_sal+200 OR sal>base_sal*2
-- 重写为
dno=3 AND (sal >1100 OR sal>base_sal+100) OR sal >ANY (1000, base_sal+200, base_sal*2)
OR转换ANY规则依赖于数据库对于ANY操作的支持情况。PostgreSQL V9.2.3和MySQLV5.6.10目前都不支持本条规则。
6.ALL/ANY转换集函数规则
将ALL/ANY谓词改写为等价的聚集函数MIN/MAX谓词操作,以更好地利用MIN/MAX操作进行优化。如:
sno > ANY (10, 2*5+3, sqrt(9))
-- 重写为
sno > sqrt(9)
通常,聚集函数MAX()、MIN()等的执行效率比ANY、ALL谓词的执行效率高。如果有索引存在,求解MAX/MIN的效率更高。
7.NOT规则
NOT谓词的等价重写。如下:
NOT (col_1 != 2) 重写为 col_1 = 2
NOT (col_1 != col_2) 重写为 col_1 = col_2
NOT (col_1 = col_2) 重写为 col_1 != col_2
NOT (col_1 < col_2) 重写为 col_1 >= col_2
NOT (col_1 > col_2) 重写为 col_1 <= col_2
如果在col_1上建立了索引,则可以用索引扫描代替原来的全表扫描,从而提高查询的效率。
8.OR重写并集规则
OR条件重写为并集操作,形如以下SQL示例:
SELECT * FROM student WHERE (sex='f' AND sno>15) OR age>18;
假设所有条件表达式的列上都有索引(即sex列和age列上都存在索引),为了能利用索引处理上面的查询,可以将语句改成如下形式:
SELECT * FROM student WHERE sex='f' and sno>15 UNION SELECT * FROM student WHERE age>18;
改写后的形式,可以分别利用列sex和age上的索引,进行索引扫描,然后再提供执行UNION操作获得最终结果。
WHERE、HAVING和ON条件由许多表达式组成,利用等式和不等式的性质,可以将它们的条件化简,但不同数据库的实现可能不完全相同。不同数据库的实现可能不同。
条件化简的方式如下:
1. 外连接消除的意义
外连接的左右子树不能互换,并且外连接与其他连接交换连接顺序时,必须满足严格的条件以进行等价变换。
查询重写的一项技术就是把外连接转换为内连接,对优化的意义如下:
PostgreSQL外连接注释表(表2-8):
分3种情况讨论表2-8,来弄明白,为什么外连接可以转换为内连接?(有点复杂,后续根据需要再详细了解)
2. 外连接消除的条件
外连接可转换为内连接的条件:WHERE子句中与内表相关的条件满足“空值拒绝”(reject-NULL条件)。一般认为下面任意一种情况满足空值拒绝:
当执行连接操作的次序不是从左到右逐个进行时,就说明这样的连接表达式存在嵌套。如:
SELECT * FROM t1 LEFT JOIN (t2 LEFT JOIN t3 ON t2.b=t3.b) ON t1.a=t2.a WHERE t1.a>1
另外一种格式用括号把连接次序做了区分:
SELECT * FROM A JOIN (B JOIN C ON B.b1=C.c1) ON A.a1=B.b1 WHERE A.a1 > 1;
-- 可以等价转换为
SELECT * FROM A JOIN B JOIN C ON B.b1=C.c1 ON A.a1=B.b1 WHERE A.a1 > 1;
综上可得以下结论:
连接的分类:
其中外链接的优化前面讨论过,下面分情况讨论其它可优化的连接。
情况一:主外键关系的表进行的连接,可消除主键表,这不会影响对外键表的查询。
如:
CREATE TABLE B (b1 INT, b2 VARCHAR(9), PRIMARY KEY(b1));
CREATE TABLE A (a1 INT, a2 VARCHAR(9), FOREIGN KEY(a1) REFERENCES B(b1));
CREATE TABLE C (c1 INT, c2 VARCHAR(9));
情况二:唯一键作为连接条件,三表内连接可以去掉中间表(中间表的列只作为连接条件)。
如:
CREATE TABLE A (a1 INT UNIQUE, a2 VARCHAR(9),a3 INT);
CREATE TABLE B (b1 INT UNIQUE, b2 VARCHAR(9),c2 INT);
CREATE TABLE C (c1 INT UNIQUE, c2 VARCHAR(9),c3 INT);
B的列在WHERE条件子句中只作为等值连接条件存在,则查询可以去掉对B的连接操作。
SELECT A.*, C.* FROM A JOIN B ON (a1=b1) JOIN C ON (b1=c1);
-- 相当于
SELECT A.*, C.* FROM A JOIN C ON (a1= c1);
情况三:其他一些特殊形式
如:
SELECT MAX(a1) FROM A, B;/* 在这样格式中的MIN、MAX函数操作可以消除连接,去掉B表不影响结果,其他聚集函数不可以*/
SELECT DISTINCT a3 FROM A, B; /* 对连接结果中的a3列执行去重操作*/
SELECT a1 FROM A, B GROUP BY a1;/* 对连接结果中的a1列执行分组操作*/
语义优化包括以下基本概念:
语义转换是根据完整性约束等信息对“某特定语义”进行推理,得到一种查询效率不同但结果相同的查询。
语义优化是从语义的角度对SQL进行优化,不是一种形式上的优化,所以其优化的范围可能覆盖其他类型的优化范围。
语义优化常见的方式如下:
非SPJ查询,查询中包含GROUPBY子句。
1. GROUPBY优化
可考虑分组转换技术,即对分组操作、聚集操作与连接操作的位置进行交换。常见方式如下:
另外,GROUPBY、ORDERBY优化的另外一个思路是尽量利用索引。
2. ORDERBY优化
对于ORDERBY的优化,可有如下方面的考虑:
3. DISTICT优化
可考虑如下方面:
逻辑优化阶段使用的启发式规则通常包括如下两类。
1. 一定能带来优化效果的,主要包括:
2. 变换未必会带来性能的提高,需根据代价选择