优化更新第一、最后和倒数第二个排名值
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索引
- (created_at)
- (user_id, created_at)
- (user_id, 输入)
- (类型,created_at)
这个可以优化吗?我觉得这可以在没有子查询的情况下完成。
因为我们在 (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'
。如果这是选择性的,那么具有相同谓词的部分索引会更好。然后我们甚至可以获得仅索引扫描 ...
由于新查询应该快很多,您可以一次更新更多(或所有)用户。
我需要缓存每个用户发生的事情的第一次、最后一次和倒数第二次。我正在查询的历史记录 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索引
- (created_at)
- (user_id, created_at)
- (user_id, 输入)
- (类型,created_at)
这个可以优化吗?我觉得这可以在没有子查询的情况下完成。
因为我们在 (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'
。如果这是选择性的,那么具有相同谓词的部分索引会更好。然后我们甚至可以获得仅索引扫描 ...
由于新查询应该快很多,您可以一次更新更多(或所有)用户。