我还是 SQL 新手,使用 PostgreSQL 16.2。
create table users(
user_id serial primary key
);
create table task_pool(
task_id serial primary key,
order_id integer,
clicker_id integer,
INDEX idx_order_id (order_id)
);
create table task_dispersal(
id serial primary key,
order_id integer,
to_disp integer
);
users 有 100,000 行(因此有 100,000 个 user_ids);
task_分散中有 1,000 行,每行 to_disp 为 10。
从任务池中的 0 行开始。
我想迭代
task_dispersal
中的行。对于每一行...
从
users
表中获取 to_disp 数量的随机 user_id,这些随机 user_id 尚未包含在 task_pool
中以及迭代行的相关 order_id 中。
例如,如果
task_dispersal
中的某行 order_id 1 和 to_disp 10,它将检查 task_pool
中 order_id 1 的所有行,从这些行中获取 user_id 并从用户表中过滤它们,然后选择10 个随机行(user_id)插入到 task_pool
。
我正在使用这个查询:
INSERT into task_pool(clicker_id, order_id)
SELECT U.user_id, TD.id
FROM task_dispersal TD
CROSS JOIN LATERAL (
SELECT Us.user_id
FROM users Us
WHERE user_id NOT IN (
SELECT clicker_id
FROM task_pool
WHERE order_id = TD.id
)
ORDER BY RANDOM()
LIMIT TD.to_disp
) U
它可以工作,而且相对较快,但我仍然希望有一种方法可以优化它,因为在我的(非常弱的)托管数据库上需要 26 秒,而我正在尝试大约几秒钟。
这是查询计划:
Insert on public.task_pool (cost=6586.23..674875.51 rows=0 width=0) (actual time=26540.859..26540.860 rows=0 loops=1)
Buffers: shared hit=32262 dirtied=17 written=18
-> Nested Loop (cost=6586.23..674875.51 rows=500000 width=24) (actual time=1929.910..26299.971 rows=1000 loops=1)
Output: nextval('task_pool_task_id_seq'::regclass), td.order_id, us.user_id, CURRENT_TIMESTAMP, 0
Buffers: shared hit=28619
-> Seq Scan on public.task_dispersal td (cost=0.00..3.00 rows=100 width=8) (actual time=0.007..0.140 rows=100 loops=1)
Output: td.id, td.order_id, td.reset_time, td.disp_remaining, td.daily_disp, td.disp_interval, td.next_disp_time, td.expired_tasks, td.to_disp
Buffers: shared hit=2
-> Limit (cost=6586.23..6598.73 rows=5000 width=12) (actual time=249.542..249.543 rows=10 loops=100)
Output: us.user_id, (random())
Buffers: shared hit=27607
-> Sort (cost=6586.23..6711.23 rows=50000 width=12) (actual time=249.538..249.539 rows=10 loops=100)
Output: us.user_id, (random())
Sort Key: (random())
Sort Method: top-N heapsort Memory: 25kB
Buffers: shared hit=27607
-> Index Only Scan using users_pkey on public.users us (cost=2.96..2181.57 rows=50000 width=12) (actual time=3.846..138.345 rows=100000 loops=100)
Output: us.user_id, random()
Filter: (NOT (hashed SubPlan 1))
Heap Fetches: 0
Buffers: shared hit=27607
SubPlan 1
-> Index Scan using idx_order_id on public.task_pool task_pool_1 (cost=0.15..2.63 rows=16 width=4) (actual time=0.003..0.003 rows=0 loops=100)
Output: task_pool_1.clicker_id
Index Cond: (task_pool_1.order_id = td.order_id)
Buffers: shared hit=106
Settings: effective_io_concurrency = '200', random_page_cost = '1.1', effective_cache_size = '192MB', max_parallel_workers = '1', max_parallel_workers_per_gather = '1', work_mem = '1703kB'
Query Identifier: 3069500943293269296
Planning:
Buffers: shared hit=18 read=2
Planning Time: 0.275 ms
JIT:
Functions: 22
Options: Inlining true, Optimization true, Expressions true, Deforming true
Timing: Generation 1.054 ms, Inlining 135.482 ms, Optimization 1098.077 ms, Emission 399.930 ms, Total 1634.542 ms
Execution Time: 26541.848 ms
我真的不知道如何阅读查询计划,但我很确定对不在带有 order_id 的 task_pool 中的
ORDER BY RANDOM()
结果使用 user_ids
成本最高?
我不认为我可以使用
tablesample bernoulli
从 users
表中获取随机用户;因为它选择的一些/所有样本行可能已经在task_pool中,所以它将插入少于所需数量(to_disp)的user_ids(这种情况在执行查询几次后经常发生)
如果我可以在
的结果上使用表样本伯努利WHERE user_id NOT IN (
SELECT clicker_id
FROM task_pool
WHERE order_id = TD.order_id
)
那就太棒了;不幸的是它只能用在表、临时表或物化视图上,并且不能在
WHERE
之后使用。
我尝试制作每行未使用的 user_ids 的临时表,然后
tablesample bernoulli
(当每行有多达 100,000 个未使用的 user_ids 时非常慢)
目前,我认为除了升级服务器之外,没有什么可以做的优化。还有更好的想法吗?
随机重新排列以避免横向,可能会有所帮助......
WITH
randomised AS
(
SELECT
U.user_id, TD.id, TD.to_disp
ROW_NUMBER() OVER (
PARTITION BY TD.id
ORDER BY RANDOM()
)
AS selection_id
FROM
task_dispersal TD
CROSS JOIN
users U
WHERE
NOT EXISTS (
SELECT *
FROM task_pool
WHERE order_id = TD.id
AND clicker_id = U.user_id
)
)
SELECT
user_id, id
FROM
randomised
WHERE
selection_id <= to_disp