为什么CTE比内联子查询差很多


11

我试图更好地了解查询计划程序在postgresql中的工作方式。

我有这个查询:

select id from users 
    where id <> 2
    and gender = (select gender from users where id = 2)
    order by latest_location::geometry <-> (select latest_location from users where id = 2) ASC
    limit 50

它在我的数据库上运行的时间不到10ms,在users表中有大约500k条目。

然后,我认为为避免重复的子选择,我可以将查询重写为CTE,如下所示:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

但是,此重写查询将在大约1秒钟内运行!为什么会这样?我在说明中看到它不使用几何索引,但是对此可以做任何事情吗?谢谢!

编写查询的另一种方法是:

select u.id, u.popularity from users u, (select gender, latest_location from users where id = 2) as me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

但是,这也将与CTE一样慢。

另一方面,如果我提取出me参数并静态插入它们,则查询又很快了:

select u.id, u.popularity from users u
    where u.gender = 'male'
    order by  u.latest_location::geometry <-> '0101000000A49DE61DA71C5A403D0AD7A370F54340'::geometry ASC
    limit 50;

解释第一个(快速)查询

 Limit  (cost=5.69..20.11 rows=50 width=36) (actual time=0.512..8.114 rows=50 loops=1)
   InitPlan 1 (returns $0)
     ->  Index Scan using users_pkey on users users_1  (cost=0.42..2.64 rows=1 width=32) (actual time=0.032..0.033 rows=1 loops=1)
           Index Cond: (id = 2)
   InitPlan 2 (returns $1)
     ->  Index Scan using users_pkey on users users_2  (cost=0.42..2.64 rows=1 width=4) (actual time=0.009..0.010 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Index Scan using users_latest_location_gix on users  (cost=0.41..70796.51 rows=245470 width=36) (actual time=0.509..8.100 rows=50 loops=1)
         Order By: (latest_location <-> $0)
         Filter: (gender = $1)
         Rows Removed by Filter: 20
 Total runtime: 8.211 ms
(12 rows)

说明第二个(慢速)查询

Limit  (cost=62419.82..62419.95 rows=50 width=76) (actual time=1024.963..1024.970 rows=50 loops=1)
   CTE me
     ->  Index Scan using users_pkey on users  (cost=0.42..2.64 rows=1 width=221) (actual time=0.037..0.038 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Sort  (cost=62417.18..63030.86 rows=245470 width=76) (actual time=1024.959..1024.963 rows=50 loops=1)
         Sort Key: ((u.latest_location <-> me.latest_location))
         Sort Method: top-N heapsort  Memory: 28kB
         ->  Hash Join  (cost=0.03..54262.85 rows=245470 width=76) (actual time=0.122..938.131 rows=288646 loops=1)
               Hash Cond: (u.gender = me.gender)
               ->  Seq Scan on users u  (cost=0.00..49353.41 rows=490941 width=48) (actual time=0.021..465.025 rows=490994 loops=1)
               ->  Hash  (cost=0.02..0.02 rows=1 width=36) (actual time=0.054..0.054 rows=1 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  CTE Scan on me  (cost=0.00..0.02 rows=1 width=36) (actual time=0.047..0.049 rows=1 loops=1)
 Total runtime: 1025.096 ms

3
我最近写过这本书。参见blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences。尽管目前存在一些DNS问题,但可能会限制该站点的可达性。尝试使用子查询FROM代替CTE,以获得最佳结果。
Craig Ringer 2014年

如果(select id, latest_location from users where id = 2)用作CTE怎么办?可能是*导致了此问题
2014年

我本来以为你会寻找异性:)最接近用户

@cha在cte中选择性别和位置不会影响速度。(就我而言,我想取相似用户的平均值,只是我简化了问题的查询)
viblo 2014年

@CraigRinger我不认为它是优化栅栏。我也尝试了您的建议,而且速度也很慢。另一方面,如果我手动提取参数,那么它很快(对于我而言,这是一个真实的选择,最终结果还是一个函数)。
viblo 2014年

Answers:


11

尝试这个:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = (select gender from me)
    order by  u.latest_location::geometry <-> (select latest_location from me)::geometry ASC
    limit 50;

当我看快速计划时,这是跳到我身上的(猛烈抨击):

 限制(成本= 5.69..20.11行= 50宽度= 36)(实际时间= 0.512..8.114行= 50循环= 1)
   InitPlan 1(返回$ 0)
     ->在用户users_1上使用users_pkey进行索引扫描(成本= 0.42..2.64行= 1宽度= 32)(实际时间= 0.032..0.033行= 1循环= 1)
           索引条件:(id = 2)
   InitPlan 2(返回$ 1)
     ->在用户users_2上使用users_pkey进行索引扫描(成本= 0.42..2.64行= 1宽度= 4)(实际时间= 0.009..0.010行= 1循环= 1)
           索引条件:(id = 2)
   ->使用用户上的users_latest_location_gix进行索引扫描(成本= 0.41..70796.51行= 245470宽度= 36)(实际时间= 0.509..8.100行= 50循环= 1)
         排序依据:(latest_location   $ 0)
         过滤条件:(性别= 1美元)
         过滤网移除的行数:20
 总运行时间:8.211毫秒
(12列)

在慢速版本中,查询计划程序在连接的上下文中评估等于运算符gender和几何运算符,其中join的值可能随每一行而变化(即使正确估计只有1行)。在快速版本中,和的值被视为标量,因为它们是由内联子查询发出的,这告诉查询计划程序每个值仅具有一个要处理的值。这与粘贴文字值时获得快速计划的原因相同。latest_locationmegenderlatest_location


我认为您现在可以me从该from条款中删除。
Jarius Hebzo '18年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.