大表会进行索引扫描,相对会快很多。


Merge join 如果数据做好了排序,而且是数字类型排序,Merge join 可能反而比 Hash 要快。但一般来说如果数据量比较大,Hash 基本会比 Merge join 更快。


另外是关联相关参数一般以 Enable 开头。刚刚那几种连接 Nested Loop、Merge join、Hash join、Bitmap Scan 都是可以去控制的,参数可以是 session 级别控制。


查看执行计划首先是看扫描方式和连接方式,不论再怎么复杂,都是通过这两个进行组合。一般是看它在扫描和关联是不是合理的。这两个判断之后,再去看它的条件是不是合理,或需不需要改写。有了执行计划之外,在看具体执行时间,就要加上 Explain Analyze 来看具体执行时间。这里有一个不一样的点,在这里有了一个实际执行时间,这个时间是真实时间。可以很精确知道每一步花费时间。


在 Analyze 之外,还有一些其它参数,可以通过\H Explain 的方式去查看详细的语法,有 verbos 显示具体执行日志,还有 Cost 消耗、Settings 显示特殊设置,buffers 内存的一些分配情况。wal、Timing 时间,Summary,format 输出的格式 TXT 或者 xml、json。如果加上,它的显示信息会多很多。主要是 buffers 比较有用,显示说你申请了多少,现在多少磁盘块是要命中,多少是进行读取的。在第二次查询的时候,它的磁盘读取会变少,第一次读取是 94 块,第二次 50 块块。


除了上述内容,还有一个日志参数。我们的 log_planner_stats 可以输出你的执行计划到日志文件中,Oracle 的执行计划是从表里去看,而我们 PG 是没有的。那么怎么办?可以通过一些参数去控制,导到日志里来。就目前这个日志它是输入到运行日志里的,没有单独去进行记录。当然这个也是我们优化的一个方向。


通过设置这些参数,把这里日志打印出来,显示出执行计划,语法分析、语义分析、重写,这几个阶段它会显示出来。如果开启了执行计划状态,会把这些进行打印。


最后看执行计划之外,从执行计划去反推 SQL 优化方向。从最底层一个扫描去入手,要尽量走索引扫描。另外索引扫描这里有很多方式,就是看它是否是合理索引,要看类型是不是选择合理的。比如数字类型、字符串类型,我们选用 gin 索引,还是一些 btree 索引。PG 默认是 btree 索引,但 btree 索引不是所有类型和操作符都会适用。另外还需要减少不必要的索引、避免单条 SQL 插入,要单条变为批量进行插入。


前面说执行计划表连接类型是不是正确合理,另外要从 SQL 本身进行入手,我们目的是为了减少它的消耗。如果 SQL 语句比较复杂,而扫描类型已经无法改动,那这时只能去改写 SQL 语句,尽量减少嵌套,减少子查询。还可以通过物化视图临时表,去做 SQL 拆分。


尽量把 in 语法用 Exits 方式做连接。另外还要注意一些类型的转换失真,在扫描时,如果它可以走索引扫描,结果走了全面扫描,可能是转换失真了,比如说一个 in 类型,结果输入是一个字符串类型,它有可能会转换失败,只能走全面扫描,不能索引。


另外从数据库参数来入手,就需要精确的统计信息,我们在生成执行计划时,可能 autovacuum 没有去执行,也可能统计信息落后,那么执行计划就是错误的。这时候就要对应表作为一个 analyze。


最后就是干涉执行计划,干涉执行计划有两种方式,除了前面的 enable 几个参数,我们的 pg_hint_plan 插件也可以做一些 Hint 控制。还有一些新参数调整,例如调整 work_mem、temp_buffers、shared_buffers 等参数。还有一些连接池的使用,我们操作系统参数、硬件的性能参数调整等等。


其实往往数据库优化,除了这些以外,还有我们去看操作系统的一些硬件性能,比如 CPU 是不是 Performance 模式,磁盘调度方式是不是最优的,网卡 Bond 模式等其他参数。