【问题标题】:Table join order in postgrespostgres中的表连接顺序
【发布时间】:2009-09-23 20:17:33
【问题描述】:

我有办法在 Postgres 中强制执行特定的加入顺序吗?

我有一个看起来像这样的查询。我已经消除了实际查询中的一堆东西,但是这种简化说明了这个问题。剩下的不应该太神秘:使用角色/任务安全系统,我试图确定给定用户是否有权执行给定任务。

select task.taskid
from userlogin
join userrole using (userloginid)
join roletask using (roleid)
join task using (taskid)
where loginname='foobar'
and taskfunction='plugh'

但我意识到程序已经知道 userlogin 的值,所以似乎可以通过跳过对 userlogin 的查找并只填写 userloginid 来提高查询效率,如下所示:

select task.taskid
from userrole
join roletask using (roleid)
join task using (taskid)
where userloginid=42
and taskfunction='plugh'

当我这样做时——从查询中删除一个表并硬编码从该表中检索到的值——解释计划时间增加了!在原始查询中,Postgres 读取 userlogin 然后 userrole 然后 roletask 然后 task。但在新查询中,它决定先读取 roletask,然后加入 userrole,尽管这需要对 roletask 进行全文件扫描。

完整的解释计划是:

版本 1:

Hash Join  (cost=12.79..140.82 rows=1 width=8) 
  Hash Cond: (roletask.taskid = task.taskid) 
  ->  Nested Loop  (cost=4.51..129.73 rows=748 width=8) 
        ->  Nested Loop  (cost=4.51..101.09 rows=12 width=8) 
              ->  Index Scan using idx_userlogin_loginname on userlogin  (cost=0.00..8.27 rows=1 width=8) 
                    Index Cond: ((loginname)::text = 'foobar'::text) 
              ->  Bitmap Heap Scan on userrole  (cost=4.51..92.41 rows=33 width=16) 
                    Recheck Cond: (userrole.userloginid = userlogin.userloginid) 
                    ->  Bitmap Index Scan on idx_userrole_login  (cost=0.00..4.50 rows=33 width=0) 
                          Index Cond: (userrole.userloginid = userlogin.userloginid) 
        ->  Index Scan using idx_roletask_role on roletask  (cost=0.00..1.50 rows=71 width=16) 
              Index Cond: (roletask.roleid = userrole.roleid) 
  ->  Hash  (cost=8.27..8.27 rows=1 width=8) 
        ->  Index Scan using idx_task_taskfunction on task  (cost=0.00..8.27 rows=1 width=8) 
              Index Cond: ((taskfunction)::text = 'plugh'::text) 

版本 2:

Hash Join  (cost=96.58..192.82 rows=4 width=8) 
  Hash Cond: (roletask.roleid = userrole.roleid) 
  ->  Hash Join  (cost=8.28..104.10 rows=9 width=16) 
        Hash Cond: (roletask.taskid = task.taskid) 
        ->  Seq Scan on roletask  (cost=0.00..78.35 rows=4635 width=16) 
        ->  Hash  (cost=8.27..8.27 rows=1 width=8) 
              ->  Index Scan using idx_task_taskfunction on task  (cost=0.00..8.27 rows=1 width=8) 
                    Index Cond: ((taskfunction)::text = 'plugh'::text) 
  ->  Hash  (cost=87.92..87.92 rows=31 width=8) 
        ->  Bitmap Heap Scan on userrole  (cost=4.49..87.92 rows=31 width=8) 
              Recheck Cond: (userloginid = 42) 
              ->  Bitmap Index Scan on idx_userrole_login  (cost=0.00..4.49 rows=31 width=0) 
                    Index Cond: (userloginid = 42) 

(是的,我知道在这两种情况下成本都很低,而且差异看起来并不重要。但这是在我从查询中消除了一堆额外的工作以简化我必须发布的内容之后。真正的查询仍然不离谱,但我对原理更感兴趣。)

【问题讨论】:

  • 你能显示查询计划(解释分析)和表定义吗?
  • 好吧,你问了,我把假设的简单示例替换为真实的查询,并添加了解释计划结果。哦,我确信我可以添加一些额外的索引来加快第二次查询,但这不是重点。考虑到 Postgres 的查询,为什么选择一个不如它所能做的最好的计划?特别是当它证明如果我使查询更复杂它可以做得更好?
  • 您是在比较查询的实际运行时间还是只查看解释计划中的标题成本?花费?值得注意的是,您刚刚发布了解释输出,而不是解释分析。尽管您预计更高的成本等同于较慢的查询运行,但可能不会这样。
  • 没错,我没有比较实际的运行时间,你当然是正确的,相对速度可能会逆转。但我认为这无关紧要:Postgres 必须根据计算的计划成本而不是实际成本来制定计划决策,并且按照它自己的标准,它选择了一个劣质计划。如果实际运行时间变得更好,那只是运气不好。

标签: sql database postgresql


【解决方案1】:

文档中的这个页面描述了如何防止 PostgreSQL 优化器重新排序连接的表,允许您自己控制连接的顺序:

http://www.postgresql.org/docs/current/interactive/explicit-joins.html

【讨论】:

  • PostgreSQL 确实拥有我见过的 RDBMS 最好的文档。
  • 你试过了吗?我当然不想为所有查询更改此设置,只针对这里和那里的一两个。如果我使用 set 语句更改它,这会影响整个数据库引擎,还是仅影响当前连接或当前事务?嗯,我想我可以通过打开两个连接来测试这个,从一个设置它,然后看看是否有其他更改的解释计划......
  • 我没试过。您是对的,在一对并发会话中自己尝试是确定的最佳方法。文档可能是错误的(尽管正如@hgiminez 指出的那样,在 PostgreSQL 的文档中很少这样)。
  • 我会给你分数,显然是教科书的正确答案。这不是我想听到的答案!我想要一些方法来做提示,比如在 Oracle 中。
【解决方案2】:

您确定您的表格统计信息是最新的吗?当 PostgreSQL 的基于成本的优化器因这些琐碎的事情而失败时,这是一个很好的迹象,表统计数据存在严重错误。解决根本原因比通过覆盖内置优化器来解决它更好,因为问题也不可避免地会在其他地方弹出。

在受影响的表上运行ANALYZE,看看它是否会让 PostgreSQL 选择不同的计划。如果它仍然选择了一些愚蠢的东西,那么查看查询计划将非常有趣。优化器没有做正确的事情通常被认为是一个错误。

【讨论】:

  • 是的。在我最初的令人惊讶的结果之后,我对这些表重新运行了分析,然后重新执行了解释计划,结果相似。
  • 这似乎有点奇怪。你的 Effective_cache_size 设置是什么?默认的 128M 可能会导致对中小型表进行不合理的顺序扫描。此外,对于高速缓存的数据库,降低 random_page_cost 可能是个好主意。
猜你喜欢
  • 1970-01-01
  • 2011-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-31
  • 2022-12-06
  • 2015-06-19
  • 2017-12-08
相关资源
最近更新 更多