七叶笔记 » 数据库 » PostgreSQL 对IN,EXISTS,ANY/ALL,JOIN的sql优化方案

PostgreSQL 对IN,EXISTS,ANY/ALL,JOIN的sql优化方案

数据准备:

IN语句

查询要求:找出那些余额(balance)大于0的每个分支(branch)在表在pgbench_accounts中有多少个账户

1.使用IN子句  2.使用ANY子句

  

3.使用EXISTS子句

  

4.使用INNER JOIN

在完成这个查询要求的时候,有人可能会假设exists和inner join性能可能会更好,因为他们可以使用两表连接的逻辑和优化。而IN和ANY子句需要使用子查询。

然而,PostgreSQL(10版本之后)已经智能的足以对上面四种写法产生相同的执行计划!

所有上面的写法都会产生相同的执行计划:

那么,我们是否可以得出这样的结论:我们可以随意地编写查询,而PostgreSQL的智能将会处理其余的问题?!

等等!

如果我们考虑排除情况,事情会变得不同。

排除查询

查询要求:找出那些余额(balance)不大于0的每个分支(branch)在表在pgbench_accounts中有多少个账户

1.使用NOT IN

执行计划:

2.使用<>ALL

执行计划:

3.使用NOT EXISTS

执行计划:

4.使用LEFT JOIN和IS NULL

执行计划:

NOT IN 和 <> ALL生成执行计划都包含了一个子查询。他们是各自独立的。

而NOT EXISTS和LEFT JOIN生成了相同的执行计划。

这些hash连接(或hash anti join)是完成查询要求的最灵活的方式。这也是推荐exists或join的原因。因此,推荐使用exists或join的经验法则是有效的。

但是,我们继续往下看! 即使有了子查询执行计划,NOT IN子句的执行时间也会更好?

是的。PostgreSQL做了出色的优化,PostgreSQL将子查询计划进行了hash处理。因此PostgreSQL对如何处理IN子句有了更好的理解,这是一种逻辑思维方式,因为很多人倾向于使用IN子句。子查询返回的行很少,但即使子查询返回几百行,也会发生同样的情况。

但是,如果子查询返回大量行(几十万行)怎么办?让我们尝试一个简单的测试:

执行计划:

这里,执行计划将子查询进行了物化。代价评估变成了15195038853.01。(PostgreSQL的默认设置,如果t2表的行低于100k,会将子查询进行hash)。这样就会严重影响性能。因此,对于那种子查询返回的行数很少的场景,IN子句可以起到很好的作用。

其它注意点

有的!在我们用不同的方式写查询的时候,可能有数据类型的转换。

比如,语句:

就会发生隐式的类型转换:

这里的(gen)::text就发生了类型转换。如果在大表上,这种类型转换的代价会很高,因此,PostgreSQL对IN子句做了更好的处理。

将IN子句转换成了ANY子句,没有对gen列进行类型转换。而是将M\F转成了bpchar(内部等价于char)

总结

简单来说,exists和直接join表通常比较好。

很多情况下,PostgreSQL将IN子句换成被hash的子计划。在一些特殊场景下,IN可以获得更好的执行计划。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持七叶笔记。如有错误或未考虑完全的地方,望不吝赐教。

相关文章