重写优化
PawSQL的重写优化引擎提供丰富的SQL重写优化,推荐语义等价,但执行效率更高的SQL语句.
正确性规则
1.ALL
修饰的子查询重写优化
规则描述
假设通过下面的SQL来获取订单系统关闭后注册的用户
select * from customer where c_regdate > all(select o_orderdate from orders)
如果子查询的结果中存在NULL,这个SQL永远返回为空。正确的写法应该是在子查询里加上非空限制,或使用max/min的写法
select * from customer where c_regdate > (select max(o_custkey) from orders)
PawSQL推荐采用第二种写法,可以通过max/min重写进一步优化SQL,获取该优化的更详细信息。
触发条件
- ALL修饰的子查询条件
2. IN可空子查询可能导致结果集不符合预期
规则描述
对于以下想要查询没有订单用户的SQL,
select * from customer where c_custkey not in (select o_custkey from orders)
如果子查询的结果集里有空值,这个SQL永远返回为空。正确的写法应该是在子查询里加上非空限制,即
select * from customer where c_custkey not in (select o_custkey from orders where o_custkey is not null)
触发条件
- 存在IN子查询条件
- IN子查询的选择列取值可以为NULL
3. NPE重写
规则描述
SQL的NPE(Null Pointer Exception)问题是指在SQL查询中,当聚合列全为NULL时,SUM、AVG等聚合函数会返回NULL,这可能会导致后续的程序出现空指针异常。譬如对于下面的SQL:
select sum(t.b) from (values row(1,null)) as t(a,b);
可以使用如下方式避免NPE问题:
SELECT IFNULL(SUM(t.b), 0) from (values row(1,null)) as t(a,b);
这会返回0而不是NULL,避免了空指针异常。
Oracle:NVL(); SQL Server和MS Access:ISNULL(); MySQL:IFNULL()或COALESCE();
触发条件
- SUM或AVG聚集函数
- 聚集函数的参数可能全为NULL, 包括
- 参数是列,列定义可以为空
- 参数是表达式,表达式可以为空
- 列定义不可为空,但是是外连接的内表,结果可能为空
4. 禁止使用=NULL判断空值
规则描述
-
= null
并不能判断表达式为空,= null
总是被判断为假。判断表达式为空应该使用is null
. -
case expr when nulll
也并不能判断表达式为空, 判断表达式为空应该case when expr is null
。在where/having的筛选条件的错误写法还比较容易发现并纠正,而在藏在case 语句里使用null值判断就比较难以被发现。
触发条件
- 语句中存在
= null
或是case when expr is null
判断逻辑
性能优化规则
1. 显式禁止结果字段排序
规则描述
在MySQL的早期版本中,即使没有order by子句,group by默认也会按分组字段排序,这就可能导致不必要的文件排序,影响SQL的查询性能。可以通过添加order by null
来强制取消排序,禁用查询结果集的排序;PawSQL识别并进行了重写。
譬如下面的例子中
SELECT l_orderkey, sum(l_quantity)
FROM lineitem
GROUP BY l_orderkey;
在MySQL 5.x版本中,group by l_orderkey
会引起默认排序, 可以通过添加order by null
来避免该排序。
SELECT l_orderkey, sum(l_quantity)
FROM lineitem
GROUP BY l_orderkey
ORDER BY NULL;
触发条件
-
MySQL数据库,版本低于8.0
-
存在分组字段,且无排序字段
2. COUNT标量子查询重写
规则描述
对于使用COUNT标量子查询来进行判断是否存在,可以重写为EXISTS子查询,从而避免一次聚集运算。譬如对于如下的SQL,
select * from customer where (select count(*) from orders where c_custkey=o_custkey) > 0
可以重写为,
select * from customer where exists(select 1 from orders where c_custkey=o_custkey)
规则描述
数据库可以利用索引的有序性来避 免ORDER子句中列的排序,从而提升SQL的性能。但是如果ORDER字段是一个表达式或函数,则可能无法利用索引来进行排序。
触发条件
- 存在COUNT标量子查询>0条件
3. 无条件的DELETE建议重写为TRUNCATE
规则描述
没有查询条件或查询条件恒真的DELETE
语句会删除表中的所有数据。DELETE
语句需要写大量日志,以便进行事务回滚及主备同步。对于大表而言,可能会导致数据库的锁定和事务阻塞,同时会占用大量的日志空间。如果确认表中的数据不再需要,可以通过TRUNCATE
表了代替DELETE
语句。TRUNCATE
比DELETE
语句更快,因为它不会记录每个删除的行,而是直接将表清空并释放空间。
delete from lineitem
重写为
truncate lineitem
触发条件
- 没有条件或条件恒真的DELETE语句
4. 隐式类型转换导致索引失效
规则描述
当条件表达式的数据类型不同时,在查询执行过程中会进行一些隐式的数据类型转换。类型转换有时会应用于条件中的常量,有时会应用于条件中的列。当在列上应用类型转换时,在查询执行期间无法使用索引,可能导致严重的性能问题。譬如对于以下的SQL,
select count(*) from ORDERS where O_ORDERDATE = current_date();
如果O_ORDERDATE
列的数据类型是CHAR(16)
,那么O_ORDERDATE
上的索引将不会被使用,导致全表扫描。解决方案通常有两个,一是ALTER TABLE
改变O_ORDERDATE
的数据类型,二是把current_date
强制换换为CHAR
类型(PawSQL提供该重写建议)。
select count(*) ORDERS where ORDERS.O_ORDERDATE = cast(current_date() as CHAR(16));
触发条件
- 条件表达式是个过滤条件,且是个可索引的过滤条件
- 过滤条件两边的数据类型不一样
- 根据数据库类型转换的优先级,数据库会优 先转换列而非常量
5. 子查询中的DISTINCT消除
规则描述
对于仅进行存在性测试的子查询,如果子查询包含DISTINCT通常可以删除,以避免一次去重操作,譬如
- IN子查询:
SELECT * FROM customer WHERE c_custkey IN (SELECT DISTINCT o_custkey FROM orders);
可以简化为:
SELECT * FROM customer WHERE c_custkey IN (SELECT o_custkey FROM orders);