LockRows 计划节点需要很长时间

时间:2021-05-08 08:55:11

标签: postgresql

我在 Postgres (emulating a work queue) 中有以下查询:

DELETE FROM work_queue
WHERE id IN ( SELECT l.id
              FROM work_queue l
              WHERE l.delivered = 'f' and l.error = 'f' and l.archived = 'f'
              ORDER BY created_at
              LIMIT 5000
              FOR UPDATE SKIP LOCKED );

在并发运行上述内容(每秒 4 个进程)以及以 10K 记录/秒的速率并发摄取到 work_queue 时,查询在 LockRow 节点上有效地成为瓶颈。

查询计划输出:

 Delete on work_queue  (cost=478.39..39609.09 rows=5000 width=67) (actual time=38734.995..38734.998 rows=0 loops=1)
   ->  Nested Loop  (cost=478.39..39609.09 rows=5000 width=67) (actual time=36654.711..38507.393 rows=5000 loops=1)
         ->  HashAggregate  (cost=477.96..527.96 rows=5000 width=98) (actual time=36654.690..36658.495 rows=5000 loops=1)
               Group Key: "ANY_subquery".id
               ->  Subquery Scan on "ANY_subquery"  (cost=0.43..465.46 rows=5000 width=98) (actual time=36600.963..36638.250 rows=5000 loops=1)
                     ->  Limit  (cost=0.43..415.46 rows=5000 width=51) (actual time=36600.958..36635.886 rows=5000 loops=1)
                           ->  LockRows  (cost=0.43..111701.83 rows=1345680 width=51) (actual time=36600.956..36635.039 rows=5000 loops=1)
                                 ->  Index Scan using work_queue_created_at_idx on work_queue l  (cost=0.43..98245.03 rows=1345680 width=51) (actual time=779.706..2690.340 rows=250692 loops=1)
                                       Filter: ((NOT delivered) AND (NOT error) AND (NOT archived))
         ->  Index Scan using work_queue_pkey on work_queue  (cost=0.43..7.84 rows=1 width=43) (actual time=0.364..0.364 rows=1 loops=5000)
               Index Cond: (id = "ANY_subquery".id)
 Planning Time: 8.424 ms
 Trigger for constraint work_queue_logs_work_queue_id_fkey: time=5490.925 calls=5000
 Trigger work_queue_locked_trigger: time=2119.540 calls=1
 Execution Time: 46346.471 ms

(对应的可视化:https://explain.dalibo.com/plan/ZaZ

有什么改进的想法吗?为什么在并发插入的情况下锁定行需要这么长时间?请注意,如果我没有对 work_queue 表进行并发插入,则查询速度非常快。

1 个答案:

答案 0 :(得分:1)

我们可以看到索引扫描返回了 250692 行,以便找到 5000 行进行锁定。所以显然我们不得不跳过 49 个其他查询的锁定行。这不会非常有效,尽管如果是静态的,它不应该像你在这里看到的那么慢。但它必须为每次尝试获取一段内存上的暂时排他锁。如果它与许多其他进程争夺这些锁,您可能会导致性能级联崩溃。

如果您每秒启动 4 个这样的语句,没有上限,并且没有等待任何先前的语句完成,那么您的情况就不稳定。你一次跑步的次数越多,它们就越会互相争斗并减速。如果完成率下降但启动间隔没有下降,那么您只会得到更多进程与更多其他进程竞争,并且每个进程都变慢。因此,一旦您被推到边缘,它可能永远无法自行恢复。

并发插入的作用可能只是为系统提供足够的嘈杂负载,让崩溃有机会站稳脚跟。当然,如果没有并发插入,您的删除操作很快就会用完要删除的内容,此时它们会非常快。

相关问题