【问题标题】:Retrieving the last message between two users检索两个用户之间的最后一条消息
【发布时间】:2014-06-12 14:46:47
【问题描述】:

考虑到这个SQLFiddle 的架构,我正在尝试使用以下查询检索两个用户之间的最后一条消息:

SELECT DISTINCT ON ("user_id") *
FROM
(
  (
  SELECT DISTINCT ON ("user_id")
    "id",
    "recipient_id" AS "user_id",
    "body",
    "read",
    "created_at"
  FROM "messages"
  WHERE "sender_id" = 1
  ORDER BY "user_id", "created_at" DESC
  )

  UNION ALL

  (
  SELECT DISTINCT ON ("user_id")
    "id",
    "sender_id" AS "user_id",
    "body",
    "read",
    "created_at"
  FROM "messages"
  WHERE "recipient_id" = 1
  ORDER BY "user_id", "created_at" DESC
  )
) AS "messages"
INNER JOIN "users" ON ("users"."id" = "messages"."user_id")
ORDER BY "user_id", "messages"."created_at" DESC
LIMIT 20;

当给定用户没有太多消息时,它按预期工作并且非常快,但是当消息数量增加并且如果消息体很大时,执行时间会变得更慢。分析执行计划会发现“瓶颈”位于这两个子查询的 ORDER BY 上,因为它必须对内存中大约 10k 行进行排序。

在为这个查询苦苦挣扎 5 小时后,我一直无法找到更快的方法来实现我想要的。我尝试在 (sender_id, created_at DESC) 和 (recipient_id, created_at DESC) 上添加索引,但显然它似乎没有帮助。

那么,我做错了什么?

谢谢

PS:这是关于执行的执行计划:http://explain.depesz.com/s/0aE

【问题讨论】:

  • 您的问题措辞让我很困惑,您说的是“两个用户之间”,但您的意思似乎是说消息“由给定用户接收或发送”?
  • 您希望收到最后(一条)消息,还是最后 20 条消息?

标签: sql performance postgresql


【解决方案1】:

我的两个提示:

  • 从 UNION 的子查询中删除 ORDER BY 子句,因为您在 UNION 上有 order 子句。
  • bodyreadusernamename 移到主查询之外,并将它们连接到新的包装器查询中的结果。

抱歉删除了双引号 ;)

SELECT s.id, user_id, body, read, s.created_at, username, name
FROM (
    SELECT DISTINCT ON (user_id) *
    FROM (
        SELECT DISTINCT ON (user_id) id, recipient_id AS user_id, created_at
            FROM messages
            WHERE sender_id = 1
        UNION ALL
        SELECT DISTINCT ON (user_id) id, sender_id AS user_id, created_at
            FROM messages
            WHERE recipient_id = 1
        ) s
    ORDER BY user_id, created_at DESC
    LIMIT 20
    ) s
JOIN users u ON (u.id = s.user_id)
JOIN messages m ON (m.id = s.id)

【讨论】:

  • 这个!非常感谢,它就像一个魅力。不用担心双引号,它们是由 orm 添加的。干杯!
【解决方案2】:

您正在合并两个大型查询,按每个子查询中的列对合并进行排序,然后仅获取前 20 个结果。如果您以与排序和限制它们的并集相同的方式对每个子查询进行排序和限制,这很可能会更快。

这可能与性能无关,但是,当这些列都将是单个值(用户的 id你正在寻找)。我错过了什么吗?

所以我认为当有很多消息时,这样的事情应该会更快:

SELECT *
FROM
(
  (
  SELECT
    -- ...
  ORDER BY "created_at" DESC
  LIMIT 20
  )
  UNION ALL
  (
  SELECT
    -- ...
  ORDER BY "created_at" DESC
  LIMIT 20
  )
) AS "messages"
INNER JOIN -- ...
ORDER BY "messages"."created_at" DESC
LIMIT 20;

通过将每个子查询限制为最近的 20 条消息,您知道在生成的(最多)40 条消息中,您拥有最近的 20 条消息。它们都可能在一个子查询中,或者都在另一个子查询中,或者每个子查询中都有一些。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-09
    相关资源
    最近更新 更多