在 PostgreSQL 中随机更新大表花费的时间太长

saf*_*fts 10 postgresql vacuum update aws-aurora

我想弄清楚为什么一个UPDATE语句需要太长时间(> 30 秒)。

这是随机的,即在大多数情况下,它在 100 毫秒内完成。但是,有时(随机)需要> 30 秒才能完成。

一些细节:

  • 我使用的是 PostgreSQL 12(实际上是 AWS Aurora)
  • 我正在一个没有流量的数据库中尝试这个,所以它不会受到同时运行的任何其他查询的影响。我也在监视日志以查看是否还有其他内容正在运行,但我什么也没看到。
  • 我试过REINDEXing、VACUUMing(和VACUUM ANALYZE),但没有任何改进
  • 我检查了锁,( log_lock_waits) 但我什么也没看到。
  • 查询在循环中执行(来自 Python 应用程序)。它执行大约 5000 次查询,在某些时候,其中一些查询似乎没有遵循某种模式,需要花费大量时间才能完成。
  • 我试过分批运行它们,但同样,有些批次随机运行时间太长。
  • 表的大小有点大,约 10000000 行和约 25 个索引。

查询:

UPDATE "my_table" SET "match_request_id" = 'c607789f-4816-4a38-844b-173fa7bf64ed'::uuid WHERE "my_table"."id" = 129624354;
Run Code Online (Sandbox Code Playgroud)

的输出 EXPLAIN (ANALYZE VERBOSE BUFFERS COSTS)

 Update on public.my_table  (cost=0.56..8.58 rows=1 width=832) (actual time=34106.965..34106.966 rows=0 loops=1)
   Buffers: shared hit=431280 read=27724
   I/O Timings: read=32469.021
   ->  Index Scan using my_table_pkey on public.my_table  (cost=0.56..8.58 rows=1 width=832) (actual time=0.100..0.105 rows=1 loops=1)
         Output: (...)
         Index Cond: (my_table.id = 130561719)
         Buffers: shared hit=7
 Planning Time: 23.872 ms
 Execution Time: 34107.047 ms
Run Code Online (Sandbox Code Playgroud)

请注意,这是EXPLAIN ANALYZE. 我很困惑,因为虽然成本真的很低,但实际运行时间却是巨大的!

我试图了解这是否是预期的,以及我是否可以以某种方式改善这种情况。欢迎任何想法,我有点用完了!

编辑:添加一些评论要求的更多信息:

“正常”更新的查询计划

 Update on public.my_table  (cost=0.43..8.45 rows=1 width=837) (actual time=2.037..2.037 rows=0 loops=1)
   Buffers: shared hit=152 read=1
   I/O Timings: read=1.225
   ->  Index Scan using my_table_pkey on public.my_table  (cost=0.43..8.45 rows=1 width=837) (actual time=0.024..0.026 rows=1 loops=1)
         Output: (...)
         Index Cond: (my_table.id = 129624354)
         Buffers: shared hit=4
 Planning Time: 1.170 ms
 Execution Time: 2.133 ms
(9 rows)
Run Code Online (Sandbox Code Playgroud)

该表有 23 个索引,6 个外键约束。3 布林,1 杜松子酒。其余的 B 树。我不确定如何检查fastupdateGIN 索引,输出\d+ index_name

 Column | Type | Key? | Definition | Storage  | Stats target
--------+------+------+------------+----------+--------------
 search | text | yes  | search     | extended |
gin, for table "public.my_table"
Run Code Online (Sandbox Code Playgroud)

jja*_*nes 10

GIN 索引具有“快速更新”机制,其中新数据以线性方式写入特定部分。一旦超过某个设置的大小(gin_pending_list_limit,它是全局设置的,但可以按索引覆盖),下一个写入索引的过程就会被分配将这些条目合并到索引的主要部分的任务,这可能导致长时间冻结该过程。对于您看到的冻结时间,您必须为 gin_pending_list_limit 设置一个相当高的设置。

如果一致延迟对您来说比整体插入/更新性能更重要,您可以禁用快速更新。或者您可以降低 gin_pending_list_limit 的值以在降低冻结时间的同时保留一些好处。

如果您需要一致性和整体效率,您可以编写一个手动进程/线程,它会打开自己的连接并select gin_clean_pending_list(...)在主更新操作进行时循环运行索引。这样你就可以从待处理列表中受益,而清理它的延迟全部加载到无关紧要的后台进程中。(Vacuum 和 autovacuum 也会清理它,但你不能真正让它们运行得足够频繁以完成足够的工作,因为它们还需要做所有其他工作。因此 gin_clean_pending_list)