我们如何使该查询更有效?

时间:2019-05-25 19:41:52

标签: sql postgresql

给定三个表profiletopicmessage,我想知道所有USER配置文件是否删除了最后一个主题消息。

如果最后一条消息没有被删除,我想得到0.50,否则(例如,最后一条消息被删除,或者个人资料从未向该主题发送消息)。

我的查询具有正确的结果,但返回〜15.000个结果行大约需要25秒。

如何提高效率?理想情况是<1秒。

SELECT
  p.id AS profile,
  topic.id AS topic,
  CASE WHEN m IS NULL THEN 0 ELSE 0.5 END AS value
FROM
  profile p
  CROSS JOIN topic
  -- latest non deleted message per topic
  LEFT JOIN message m ON (
    m.profile_id = p.id
    AND m.topic_id = topic.id
    AND m.deleted = FALSE
    AND NOT EXISTS (
      SELECT m2 FROM message m2
      WHERE m2.profile_id = p.id AND m.topic_id = m2.topic_id AND m.timestamp < m2.timestamp
    )
  )
WHERE 
  p.type = 'USER'
;

EXPLAIN 的结果

Hash Left Join  (cost=395.85..1187910.62 rows=15204 width=48)
  Hash Cond: ((p.id = m.profile_id) AND (topic.id = m.topic_id))
  Join Filter: (NOT (SubPlan 1))
  ->  Nested Loop  (cost=0.00..213.67 rows=15204 width=24)
        ->  Seq Scan on profile p  (cost=0.00..22.36 rows=724 width=8)
              Filter: ((type)::text = 'USER'::text)
        ->  Materialize  (cost=0.00..1.31 rows=21 width=16)
              ->  Seq Scan on topic  (cost=0.00..1.21 rows=21 width=16)
  ->  Hash  (cost=223.15..223.15 rows=11513 width=89)
        ->  Seq Scan on message m  (cost=0.00..223.15 rows=11513 width=89)
              Filter: (NOT deleted)
  SubPlan 1
    ->  Seq Scan on message m2  (cost=0.00..309.51 rows=1 width=0)
          Filter: ((m."timestamp" < "timestamp") AND (profile_id = p.id) AND (m.topic_id = topic_id))

附注:我们需要经常执行查询,结果将被插入到另一个表(INSERT INTO ... SELECT (s. above))中以进行进一步处理。


解决方案

查看答案!

添加索引后,我将所有三个版本执行了混合10次。我正在其他计算机运行时在本地计算机上进行比较,因此它不是很科学-但结果似乎仍然很重要:

// results in ms
user          | min | max | avg  | portion of profiles that has type='USER'
Stuck         | 171 | 216 | ~180 | ~96%
Gordon Linoff | 148 | 172 | ~160 | ~96%
sticky bit    | 113 | 126 | ~120 | ~96% <-- winner
Gordon Linoff |  73 | 114 |  ~90 |  ~4% <-- winner when p.type='USER' is very selectiv

谢谢:)

2 个答案:

答案 0 :(得分:2)

  

如果未删除最后一条消息,则我希望得到0.5和0(即,最后一条消息已删除,或者个人资料从未向该主题发送消息)。

我在想一些与stickybit类似的东西,但是措辞有些不同:

select p.id as profile, t.id as topic,
       (case when not (select m.deleted
                       from messages m
                       where m.profile_id = p.id and
                             m.topic_id = t.id
                       order by m.timestamp desc
                       limit 1
                      )
             then 0.5
             else 0
         end) as value
from profile p cross join
     topic t
where p.type = 'user';

需要相同的索引:

  • messages(profile_id, topic_id, timestamp desc, deleted)
  • profile(type, id)

为什么这样说? distinct on使用索引很快。但是,我怀疑简单的索引查找会更快。

第二,您没有指定type = 'user'的选择性。此版本不处理其他配置文件上的消息,仅处理您关心的配置文件。

答案 1 :(得分:1)

嗯,也许尝试重写它,以便左联接使用一个子查询,该子查询仅包含每个主题和配置文件使用DISTINCT ON删除的最后一条消息的状态。

SELECT p.id profile,
       t.id topic,
       CASE
         WHEN coalesce(x.deleted,
                       true) THEN
           0
         ELSE
           0.5
       END value
       FROM profile p
            CROSS JOIN topic t
            LEFT JOIN (SELECT DISTINCT ON (m.profile_id,
                                           m.topic_id)
                              m.profile_id,
                              m.topic_id,
                              m.deleted
                              FROM message m
                              ORDER BY m.profile_id ASC,
                                       m.topic_id ASC,
                                       m.timestamp DESC) x
                      ON x.profile_id = p.id
                         AND x.topic_id = t.id
       WHERE p.type = 'USER';

为此,以下指标应该很有希望。

CREATE INDEX message_pid_tid_ts_d
             ON message (profile_id ASC,
                         topic_id ASC,
                         timestamp DESC,
                         deleted ASC);
CREATE INDEX profile_t_id
             ON profile (type ASC,
                         id ASC);