2

由于我认为子查询,我有一个 postgrsql 查询需要很长时间才能执行(5 分钟)。我想找到一种方法来增强这个查询:

select v.id, v.pos, v.time,  v.status,  vi.name,vi.type,
        (select c.fullname
           from company c
          where vi.registered_owner_code = c.owcode ) AS registered_owner
       ,(select c.fullname
           from company c
         where vi.group_beneficial_owner_code=c.owcode) AS group_beneficial_owner
       ,(select c.fullname
           from company c
          where vi.operator_code = c.owcode ) AS operator
       ,(select c.fullname
           from company c
          where vi.manager_code = c.owcode ) AS manager
  from (car_pos v left join cars vi on v.id = vi.id)
 where age(now(), v.time::time with time zone) < '1 days'::interval
4

5 回答 5

2

因为我认为子查询

这不是一个真正的猜谜游戏。您可以在 pgadmin 或控制台下获得查询执行计划说明

http://www.pgadmin.org/docs/1.4/query.html

http://www.postgresql.org/docs/current/static/sql-explain.html

然后你就可以看到发生了什么以及什么需要这么多时间。

分析后,您可以添加索引或更改其他内容,但至少您会知道需要更改的内容。

于 2012-12-01T12:17:44.033 回答
1

3个关键成分:

  1. 取消相关的子查询JOIN改用 - 就像已经提到的其他答案一样。

  2. WHERE子句中,不要在列上使用无法使用索引的表达式。@Frank 已经提到过。只有最基本的、稳定的表达式可以被查询规划器重写以使用索引。看看我是如何重写的。

  3. 创建合适的索引

SELECT v.id, v.pos, v.time, v.status, c.name, c.type
      ,r.fullname AS registered_owner
      ,g.fullname AS group_beneficial_owner
      ,o.fullname AS operator
      ,m.fullname AS manager
FROM   car_pos v
LEFT   JOIN cars    c ON USING (id)
LEFT   JOIN company r ON r.owcode = c.registered_owner_code
LEFT   JOIN company g ON g.owcode = c.group_beneficial_owner_code
LEFT   JOIN company o ON o.owcode = c.operator_code
LEFT   JOIN company m ON m.owcode = c.manager_code
WHERE  v.time > (now() - interval '1 day');

您需要唯一的索引cars.idcompany.owcode(主键也可以完成这项工作)。

你需要一个car_pos.time类似的索引:

CREATE INDEX car_pos_time_idx ON car_pos (time DESC);

也可以不按降序工作。如果您有很多行(-> 大表、大索引),您可能希望创建一个仅涵盖最近历史记录的部分索引,并在每天或每周的下班时间重新创建它:

CREATE INDEX car_pos_time_idx ON car_pos (time DESC);
WHERE time > $mydate

其中 $mydate 是(now() - interval '1 day'). 这可以随时在逻辑上匹配您的查询条件。效果会随着时间慢慢恶化。

另外:不要命名timestamp“时间”类型的列,从文档的角度来看这是一种误导。实际上,根本不要time用作列名。它是每个 SQL 标准中的保留字,也是 PostgreSQL 中的类型名称。

于 2012-12-01T17:25:01.167 回答
1

WHERE 条件不能使用索引,您必须更改该索引。v.time 不应该在一个 volatile 函数中,在这种情况下是 age()。

于 2012-12-01T12:19:57.157 回答
0
select v.id, v.pos, v.time,  v.status,  vi.name,vi.type,
c1.fullname as Registered_owner,
c2.fullname as group_beneficial_owner,          
c3.fullname AS operator,   
c4.fullname AS manager 

from car_pos v 
left outer join cars vi on v.id = vi.id
left outerjoin company c1 on vi.registered_owner_code=c1.owcode
left outerjoin company c2 on vi.group_beneficial_owner_code=c2.owcode
left outerjoin company c3 on vi.operator_code=c3.owcode
left outerjoin company c4 on vi.manager_code=c4.owcode
 where age(now(), v.time::time with time zone) < '1 days'::interval
于 2012-12-01T12:21:43.593 回答
0

一种简单的解决方案是将其转换为连接

select v.id, v.pos, v.time,  v.status,  vi.name,vi.type, 
reg_owner.fullname AS registered_owner, 
gr_ben_owner.fullname AS group_beneficial_owner, 
op.fullname AS operator, 
man.fullname AS manager
from  
  car_pos v 
  left join cars vi on v.id = vi.id
  left join company reg_owner on vi.registered_owner_code = reg_owner.owcode
  left join company gr_ben_owner on vi.group_beneficial_owner_code = gr_ben_owner.owcode
  left join company op on vi.operator_code = op.owcode
  left join company man on vi.manager_code  = man.owcode
where age(now(), v.time::time with time zone) < '1 days'::interval

但是,我怀疑,仅对表 Company 进行一次连接可能是可能的...我不能 100% 确定确切的语法,并且我怀疑这会提高性能(因为所有 CASE- WHEN、GROUP by 等)与四次加入解决方案相比,但我认为这也应该有效。(我假设,这 cars-car_pos 是一对一的关系)

select v.id, MAX(v.pos) as pos, MAX(v.time) as vtime,  MAX(v.status) as status,  MAX(vi.name) as name,MAX(vi.type) as type, 
MAX(CASE WHEN c.owcode = vi.registered_owner_code THEN c.fullname END) AS registered_owner, 
MAX(CASE WHEN c.owcode = vi.group_beneficial_owner_code THEN c.fullname END) AS group_beneficial_owner, 
MAX(CASE WHEN c.owcode = vi.operator_code THEN op.fullname END) AS operator, 
MAX(CASE WHEN c.owcode = vi.manager_code THEN man.fullname END) AS manager
from  
  car_pos v 
  left join cars vi on v.id = vi.id
  left join company c on c.owcode IN (vi.registered_owner_code, vi.group_beneficial_owner_code, vi.operator_code, vi.manager_code)
group by v.id
having age(now(), vtime::time with time zone) < '1 days'::interval

如果您可以将表创建 DDL 脚本和一些插入到问题中,那么在 SQL 小提琴中尝试会很容易......

于 2012-12-01T12:14:41.777 回答