优化更新第一、最后和倒数第二个排名值

Optimize updating first, last, and second to last ranked value

我需要缓存每个用户发生的事情的第一次、最后一次和倒数第二次。我正在查询的历史记录 table 有数亿行(我们正在缓存以便我们可以截断它),而我正在更新的 table 有数千万行。

目前,我正在以 1000 个为一组进行操作,以避免锁定 table。查询是这样的:

with ranked as (
  select
      user_id,
      rank() over (partition by user_id order by created_at desc) as ranked_desc,
      rank() over (partition by user_id order by created_at asc) as ranked_asc,
      created_at
  from history
  where type = 'SomeType' and
        user_id between  and 
)
update
  users u
set
  latest_at = (
    select created_at
    from ranked
    where ranked.ranked_desc = 1 and ranked.user_id = u.id
  ),
  previous_at = (
    select created_at
    from ranked
    where ranked.ranked_desc = 2 and ranked.user_id = u.id
  ),
  first_at = (
    select created_at
    from ranked
    where ranked.ranked_asc = 1 and ranked.user_id = u.id
  )
from ranked
where u.id = ranked.user_id

历史上的相关索引是这些。都是btree索引

这个可以优化吗?我觉得这可以在没有子查询的情况下完成。

因为我们在 (user_id, created_at) 上有最重要的索引,我建议:

UPDATE users u
SET    first_at    = h.first_at
     , latest_at   = h.latest_at
     , previous_at = h.previous_at
FROM  (
   SELECT u.id, f.first_at, l.last[1] AS latest_at, l.last[2] AS previous_at
   FROM   users u
   CROSS  JOIN LATERAL (
      SELECT ARRAY (
         SELECT h.created_at
         FROM   history h
         WHERE  h.user_id = u.id
         AND    h.type = 'SomeType'  -- ??
         ORDER  BY h.created_at DESC
         LIMIT  2
         ) AS last
      ) l
   CROSS  JOIN LATERAL (
      SELECT created_at AS first_at
      FROM   history h
      WHERE  h.user_id = u.id
      AND    h.type = 'SomeType'  -- ??
      ORDER  BY created_at
      LIMIT  1
      ) f
   WHERE  u.id BETWEEN  AND 
   ) h
WHERE  u.id = h.id
AND   (u.first_at    IS DISTINCT FROM h.first_at
    OR u.latest_at   IS DISTINCT FROM h.latest_at
    OR u.previous_at IS DISTINCT FROM h.previous_at);

这也适用于每个 user_id 的非唯一时间戳。

如果每个用户有很多行,非常 效率很高。它旨在避免对大 table 进行顺序扫描,而是大量使用 (user_id, created_at) 上的索引。 相关:

  • Optimize GROUP BY query to retrieve latest row per user

假设大多数或所有用户都以这种方式更新,我们不需要 users 上的索引。 (为了这个 UPDATE 的目的,没有索引是最好的。)

如果用户在 table history 中只有一行,则 previous_at 设置为 NULL。 (您的原始查询具有相同的效果。)

只有找到符合条件的历史记录行的用户才会更新。

此添加的 WHERE 子句跳过不会更改任何内容的更新(全额费用):

AND   (u.first_at    IS DISTINCT FROM h.first_at
    OR u.latest_at   IS DISTINCT FROM h.latest_at
    OR u.previous_at IS DISTINCT FROM h.previous_at)

参见:

  • How do I (or can I) SELECT DISTINCT on multiple columns?

唯一的不安全感是WHERE type = 'SomeType'。如果这是选择性的,那么具有相同谓词的部分索引会更好。然后我们甚至可以获得仅索引扫描 ...

由于新查询应该快很多,您可以一次更新更多(或所有)用户。