最近木匠在知乎上发起了一个SQL优化挑战赛,其中题目3用到了OR条件转化为UNION、隐式类型转化导致索引失效、LIMIT子句下推优化三个PawSQL的重写优化算法以及索引创建的策略。
下图是优化前后执行计划以及执行时间的对比,可以看到最终执行时间降低为原来的1/500,性能提升了500倍!本文详述其优化过程,同时一探PawSQL引擎内部优化机制。

本文所使用的执行计划可视化工具为 PawSQL Explain Visualizer , 支持MySQL、PostgreSQL、openGauss等数据库。
题目:下面的SQL如何优化性能最佳
select * from orders
where O_ORDERDATE>'2022-01-01' and (O_ORDERPRIORITY = 1 or O_SHIPPRIORITY = 1)
order by O_ORDERDATE desc
limit 20, 10;表定义:
-- tpch.orders definition
create table orders (o_orderkey int,o_custkey int,o_orderstatus char(1),o_totalprice decimal(15,2),o_orderdate date,o_orderpriority char(15),o_clerk char(15),o_shippriority int,o_comment varchar(79), primary key (o_orderkey)
) engine=innodb default charset=utf8mb4 collate=utf8mb4_0900_ai_ci;说明:此表为tpch测试集中的订单(orders)表,数据量及数据分布情况如下,
orders表总共20+w行,o_orderdate)在2021-01-01至2023-01-01基本均匀分布o_orderpriority=1的筛选率在5%左右,O_ORDERDATE>'2022-01-01',筛选率在0.1%左右;o_shippriority=1筛选率0.1%。表orders除了主键,其他列上无索引。从执行计划可以看到,会进行全表扫描,执行时间为149.51ms,其中全表扫描花费了128ms,占总执行时间的84%,需重点优化。

orders表的O_ORDERDATE字段上添加一个索引,
create index o_custkey_idx on orders(O_ORDERDATE);o_custkey_idx,数据库进行了Index range scan, 从20w的数据中过滤出1489行数据,大大降低的数据量。同时利用 o_custkey_idx的有序性,避免了一次排序操作, 执行时间降低为3.08ms。
性能已经非常好了,如果在生产环境,此轮优化到这里就可以结束了。但是作为SQL优化挑战赛,此SQL还有进一步的优化空间吗?我们接着往下看。
O_SHIPPRIORITY和O_ORDERPRIORITY的筛选率都比较高,考虑利用他们提前过滤数据OR条件重写为UNIONUNION分支中的条件创建索引
select * from ( select * from tpch.orders as o where o.O_ORDERDATE > '2022-01-01' and o.O_SHIPPRIORITY = 1) union select * from tpch.orders as o where o.O_ORDERDATE > '2022-01-01' and o.O_ORDERPRIORITY = 1 ) as dt order by dt.O_ORDERDATE desc limit 20, 10;
CREATE INDEX PAWSQL_IDX1858775370 ON tpch.orders(O_SHIPPRIORITY,O_ORDERDATE); CREATE INDEX PAWSQL_IDX0912695993 ON tpch.orders(O_ORDERPRIORITY,O_ORDERDATE);优化效果: 我们可以看到SQL并没有按照预期性能提升,反而执行时间更久了,根据下面的执行计划分析我们看到第二个子查询并没有按照预期走索引(PAWSQL_IDX0912695993),反而是进行了全表扫描,执行时间变为141.38ms,性能大大降低。我们需要分析一下为什么没有按照预期走索引。

PAWSQL_IDX0912695993o.O_ORDERPRIORITY = 1, 由于列o.O_ORDERPRIORITY是字符型,真实执行的过程中是先把列o.O_ORDERPRIORITY值转化为int型再进行计算,即int(o.O_ORDERPRIORITY) = 1。
分析出这个原因,针对性的优化是把o.O_ORDERPRIORITY = 1重写为o.O_ORDERPRIORITY = '1'。
limit子句拷贝粘贴到各个SQL的分支,需要将offset转移到limit上。即把limit 20, 10转化为limit 30limit子句下推,最终的执行时间降低为0.2ms,相比较第三步提升10倍。整个优化流程走下来,执行时间从149ms降低到0.2ms,性能提升了700多倍。
我们将待优化SQL直接提交到PawSQL,让其给我们做自动优化。从输出的优化详情页面我们可以看到,PawSQL自动帮我们进行了以下三个重写优化
OR条件重写为UNION并且根据重写后的SQL推荐了对应的索引。使用PawSQL,真正做到了一键优化!
PawSQL专注数据库性能优化的自动化和智能化,支持MySQL,PostgreSQL,Opengauss等,提供的SQL优化产品包括