前几天发现有个 sql 跑的超慢,第一次拿到 sql 大家简单分析了一下,觉得是写的有问题,里面有对一个大表的查询,数据量大概 800 万,结果还和好几个小表做了 join,而且还是 left join,速度可想而知了。单独对那个大表查询,其实也就是几分钟的事情。



所以建议就是先对小表做 join,然后再和大表做一次 join。不过结果并不理想,时间依然还是那么长。这个时候就得仔细看执行计划了,如下。



能看到虽然人肉对 sql 做了一些优化,但是 sql 并没有按照我们的期望去执行,执行计划里面还是首选去查 fact_tuan_rank_detail 这个大表,速度肯定慢了。


 Nested Loop Left Join  (cost=447.90..1003.43 rows=2 width=620)
   Join Filter: (team.id = team_arrive_city.team_id)
   ->  Nested Loop  (cost=77.62..85.98 rows=1 width=588)
         ->  HashAggregate  (cost=77.62..77.68 rows=1 width=71)
               ->  Index Scan using date_idx on fact_tuan_rank_detail  (cost=0.00..77.60 rows=1 width=71)
                     Index Cond: ((thedate >= '2012-02-25'::date) AND (thedate <= '2012-02-27'::date))
                     Filter: (((source)::text ~~ '%team%'::text) AND ((source)::text !~~ '%today%'::text) AND ((source)::text !~~ '%ongoing%'::text) AND ((s
ource)::text !~~ '%special%'::text))
         ->  Index Scan using team_pkey on team  (cost=0.00..8.28 rows=1 width=32)
               Index Cond: (team.id = fact_tuan_rank_detail.team_id)
               Filter: ((to_timestamp((team.end_time)::double precision))::date > '2012-02-27'::date)
   ->  HashAggregate  (cost=370.29..589.15 rows=14591 width=15)
         ->  Seq Scan on team_arrive_city  (cost=0.00..288.19 rows=16419 width=15)
(12 rows)


仔细研究之后,发现了 rows=1 这个信息。这就是为什么查询分析器先对这个表做查询了,因为他认为这个表最小。



此后对这个表执行了一下 analyse 命令,更新了一些统计信息。然后再看执行计划如下。


 Hash Join  (cost=1210761.12..1326052.45 rows=2282704 width=620)
   Hash Cond: (fact_tuan_rank_detail.team_id = team.id)
   ->  HashAggregate  (cost=1203912.40..1265555.26 rows=1027381 width=71)
         ->  Index Scan using date_idx on fact_tuan_rank_detail  (cost=0.00..1075489.81 rows=10273807 width=71)
               Index Cond: ((thedate >= '2012-02-25'::date) AND (thedate <= '2012-02-27'::date))
               Filter: (((source)::text ~~ '%team%'::text) AND ((source)::text !~~ '%today%'::text) AND ((source)::text !~~ '%ongoing%'::text) AND ((source)
::text !~~ '%special%'::text))
   ->  Hash  (cost=6666.33..6666.33 rows=14591 width=64)
         ->  Merge Left Join  (cost=6414.63..6666.33 rows=14591 width=64)
               Merge Cond: (team.id = b.team_id)
               ->  Sort  (cost=4670.40..4686.82 rows=6567 width=32)
                     Sort Key: team.id
                     ->  Seq Scan on team  (cost=0.00..4254.02 rows=6567 width=32)
                           Filter: ((to_timestamp((end_time)::double precision))::date > '2012-02-27'::date)
               ->  Sort  (cost=1744.23..1780.71 rows=14591 width=40)
                     Sort Key: b.team_id
                     ->  Subquery Scan on b  (cost=370.29..735.06 rows=14591 width=40)
                           ->  HashAggregate  (cost=370.29..589.15 rows=14591 width=15)
                                 ->  Seq Scan on team_arrive_city  (cost=0.00..288.19 rows=16419 width=15)
(18 rows)


可以看到执行计划已经变了,先做其他表的 join,最后再和大表 join。并且提示的执行时间也大致靠谱。



从这里面引申一下,时常会听到有人说 explain 命令执行后得出的执行时间不靠谱,需要使用 explain analyse。可是为什么不靠谱呢,其实 explain analyse 需要的时间和实际执行时间一样,explain 不靠谱的原因是因为数据库对那个表的统计信息不及时导致的。



再进一步了解,postgres 里面这个统计信息为什么不靠谱呢?难道还总是需要我维护这些信息啊?



其实 postgres 里面有个 autovacuum 进程就是做这个事情的。autovacuum 进程默认是启用的。他会在数据库空闲的时候,对数据库做 vavcuum 和 analyse。具体多久执行一次,文档 里面都有写,建议多看看这个页面里面的信息。



此外,还发现 postgres 还提供了很多 数据库状态查询函数 ,使用这里面函数可以查到每个表最后一次 analyse 的时间,vacuum 的时
间,里面索引被使用的情况等等,好多信息。


ps: 使用 analyse 之后,那个 sql 好用了,可是发现过两天又不行了,查看 explain select * from t1 好像没问题,那怎么回事呢?开始没想明白,只好继续 analyse 一下,又好了。可过了两天又不行了。这次得细看了。最后发现是因为真实的 sql 是有 where 条件的,日期条件限定的那部分数据查询分析器认为很少导致了问题。没办法后面只好每次导数都 analyse 一下了。发现 pg_bulkload 导数的方式有点问题。