openGauss

开源数据库

openGauss社区官网

开源社区

Oracle到openGauss的SQL语句级调优

DataRinpoche2024-03-24SQL调优

针对Oracle到openGauss迁移后的性能差异,由于SQL优化引擎的变动,我们往往需要进行语句级的SQL调优,以下我个人在实际项目中总结的一些常见的优化点。

获取执行计划

SQL调优的主要途径为根据执行计划进行SQL调优,在openGauss中可以通过explain sql获取指定SQL的执行计划

短期内可执行完成的SQL,获取实际执行计划,命令如下

explain analyze sql_text;

短期内无法执行完成的SQL,获取估算执行计划,命令如下

explain sql_text;

执行计划可视化解析工具

pev2是一款开源的优秀的Postgresql 执行计划可视化工具,我们同样可以用来进行openGauss 执行计划的呈现,下载如下html文件

https://www.github.com/dalibo/pev2/releases/latest/download/index.html

使用浏览器打开该文件,将上部获取到的执行计划文本粘贴至Plan输入框内,点击Submit,可直接生成可视化执行计划树

对于慢SQL,我们首要关注节点耗时,点击左侧time按钮,各节点耗时将通过条形图显示,我们选取耗时最高的sql 节点,进行分析及优化

如上样例执行计划中,耗时最高的SQL 节点如下

可以发现该节点下,存在大量seq scan即顺序全表扫描,如果为实际SQL,我们可以在相应表上添加索引以提升性能

常见SQL优化点

更新表统计信息

openGauss中执行计划生成依赖于表统计信息,表统计信息越及时越准确,生成的执行计划往往越精确越优。但由于表数据的更新规模与比率问题,运行时间较长的数据库中,表统计信息往往会出现偏差,导致SQL优化器错误估算代价,生成较慢的执行计划,因此,我们需要定期手工进行表统计信息的更新,执行如下命令

analyze table_name;

DBA可创建定时任务或脚本,在系统空闲期间进行重要表或全量表的统计信息刷新动作

改写SQL中的包/自定义函数引用

openGauss具备出色的oracle/mysql兼容性,目前支持自定义函数/存储过程/包等对象的使用,因而兼容在sql中引用自定义函数/包。但受限于当前执行计划优化器的解析,如果sql中引用的包/函数存在表查询操作等,执行计划无法将包/函数内的sql与外层SQL优化,导致性能极度劣化,因此,我们需要将包/函数内的SQL提取,与外层SQL结合,改写为直查的SQL以提升性能。

添加缺失的索引

重点关注执行计划中join条件(对应sql 中on子句)与filter条件(对应sql中where)子句,如果出现seq scan即全表扫描,可以考虑在相应的表字段添加合适的索引,以提升性能

类型不一致导致索引失效

在多表连接查询中,连接条件在不同表中有可能会存在数据类型不一致的情况,如A表为varchar,b表为int,即使两个字段都存在索引,也会因为类型转换导致索引失效,我们可以考虑在驱动表字段添加类型转换使索引生效

如样例 t1表 c为int,t2表c为varchar,两个字段都具有索引,在如下sql中也会失效

select t1.a,t2.b 
from t1 
join t2 on t2.c=t1.c
where t1.d='condition'

改写为

select t1.a,t2.b 
from t1 
join t2 on t2.c=t1.c::varchar
where t1.d='condition'

手工指定hint

受限于执行计划生成的智能度,部分复杂的多表关联SQL中,即使表统计信息准确、sql语法没有问题,生成的执行计划有可能不走索引,或没有进行合理的表查找顺序,针对该类情况,我们可以通过sql hint的方式,强制执行计划走最优计划 ,语法如下

/*+ <plan hint>*/

详细的hint语法,请参照openGauss官方文档

https://docs-opengauss.osinfra.cn/zh/docs/5.0.0/docs/PerformanceTuningGuide/Join%E9%A1%BA%E5%BA%8F%E7%9A%84Hint.html

子查询改写为join

针对结果集返回条数较多的SQL查询,如果select子句中存在子查询,且子查询存在索引,可以考虑将子查询改写为join,避免逐行表扫描调用,尤其是子查询存在聚合查询如count/max/min等操作

样例SQL

select t1.a,
(select max(t2.a) from t2 where t2.b=t1.b)
from t1 where t1.c='condition'

可以考虑改写为

select t1.a,
t3.max_value
from t1 where t1.c='condition'
join (select max(t2.a) max_vlaue ,t2.b from t2 group by t2.b) t3
on t3.b=t1.b

in/not in子查询改写为exists/not exists

where条件通过in/not in (select a from b)子查询进行条件过滤,执行计划往往会生成半嵌套查询,性能较差,我们可以等价改写为exists/not exists或更进一步改写为join进行优化

or条件改写为union/union all

复杂sql查询中,有可能会在where中出现or条件,如and ((xxxx and exists) or (xxxx and xxxx)),该种句式往往会存在性能问题,针对该类问题,我们可以考虑改写为union 或union all

select * from t1 where t1.a='condition'  and ((xxxx and exists) or (xxxx and xxxx))

改写为

select * from t1 where t1.a='condition'  and (xxxx and exists)
union /union all --根据是否会存在重复选取
select * from t1 where t1.a='condition'  and (xxxx and xxxx)

避免视图的深层嵌套

在当前版本openGauss下,如果join表中存在视图的多重嵌套,或join 条件存在uinon等情况,SQL优化器无力将外层的过滤条件或关联条件下沉,会导致视图内查询的全表扫描,返回过大的中间结果集,导致CPU占用飙升,查询性能较慢,我们需要提取视图的定义SQL,进行等价改写

如存在视图VA,定义为

select vb.a,vb.b 
from vb 
join tm
on vb.c=tm.c

视图VB定义如下

select tx.a,tx.b,ty.a
from tx 
left join ty
on tx.c=ty.c  

存在SQL如下,该sql往往会存在性能问题

sql
select t1.*,va.* 
from t1 
join va 
on va.a=t1.a 
where t1.b='condition'

改写如下

sql
select t1.*,va.* 
from t1 
join (select vb.a,vb.b 
from (select tx.a,tx.b,ty.a
from tx 
left join ty
on tx.c=ty.c ) vb 
join tm
on vb.c=tm.c) va  
on va.a=t1.a 
where t1.b='condition'

他山之石可以攻玉

对于原oracle数据库执行性能正常而openGauss性能劣化的SQL,如果单纯分析openGauss执行计划无法发现性能瓶颈或优化点,我们可以考虑参照oracle的执行计划进行比对,发现openGauss执行计划中的问题,从而进行SQL优化