[英]Very slow updates and deletes on unreferenced PostgreSQL table with low row count
我有一個 PostgreSQL 13 數據庫,其中包含一個名為cache_record
的表,托管在 Amazon RDS 上。
這是表的定義:
CREATE TABLE cache_record
(
key text NOT NULL,
type text NOT NULL,
value bytea NOT NULL,
expiration timestamptz NOT NULL,
created_at timestamptz NOT NULL DEFAULT NOW(),
updated_at timestamptz NOT NULL DEFAULT NOW(),
CONSTRAINT cache_record_pkey PRIMARY KEY (key)
)
WITH (
OIDS = FALSE
);
CREATE INDEX cache_record_expiration_idx
ON cache_record USING btree
(expiration ASC NULLS LAST);
該表本身未被任何外鍵引用(因此沒有索引/觸發問題)並且僅包含約 30000 行。 每行的value
字段長度不超過 1 MB,50% 的行小於 50 字節。 通常,DELETE 是這樣執行的:
DELETE FROM cache_record
WHERE expiration < NOW();
表中大約有 10000 個過期行要刪除。 但是這個查詢執行時間太長,運行它的批處理超時。 所以我決定分批拆分,從一個shell手動執行:
DELETE FROM cache_record
WHERE key IN (SELECT key
FROM cache_record
WHERE expiration < NOW()
ORDER BY created_at
LIMIT 100)
一批 100 行需要大約 30 秒才能執行,這是荒謬的。 嵌套的 SELECT 本身比嵌套的 DELETE(有或沒有 LIMIT)執行得快很多。
直到昨天,當本應從表中清除條目的 CRON 批處理開始超時(30 秒)時,該查詢才引起任何問題。 雖然,完全有可能查詢一直很慢,但直到昨天才剛好低於超時閾值。
什么可能導致緩慢?
編輯 2023-01-20
我按照評論中的建議使用 EXPLAIN 運行查詢:
EXPLAIN (ANALYSE, BUFFERS) DELETE FROM cache_record WHERE expiration < NOW();
我昨天清除了表,所以查詢只有幾次命中,但這足以顯示速度問題(> 10 秒的執行時間):
Delete on cache_record (cost=14.28..501.73 rows=257 width=6) (actual time=10595.107..10595.109 rows=0 loops=1)
Buffers: shared hit=200819 read=43245 dirtied=42783 written=9470
I/O Timings: read=3037.437 write=73.217
-> Bitmap Heap Scan on cache_record (cost=14.28..501.73 rows=257 width=6) (actual time=0.528..29.769 rows=551 loops=1)
Recheck Cond: (expiration < now())
Heap Blocks: exact=88
Buffers: shared hit=10 read=85 dirtied=34 written=21
I/O Timings: read=2.006 write=0.161
-> Bitmap Index Scan on cache_record_expiration_idx (cost=0.00..14.22 rows=257 width=0) (actual time=0.030..0.031 rows=551 loops=1)
Index Cond: (expiration < now())
Buffers: shared hit=7
Planning:
Buffers: shared hit=56
Planning Time: 0.324 ms
Execution Time: 10595.676 ms
你有幾個選擇。 最好的選擇是運行批量刪除,這樣就不會觸發觸發器。 在刪除之前禁用觸發器,然后重新啟用它們。 這可以為您節省大量時間。 例如:
ALTER TABLE 表名 DISABLE TRIGGER ALL;
刪除...;
ALTER TABLE 表名 ENABLE TRIGGER ALL;
這里的一個主要關鍵是你想最小化子查詢的深度。 在這種情況下,您可能希望設置臨時表來存儲相關信息,這樣您就可以避免對刪除進行深度子查詢。
基於僅在 DELETE 節點上顯示的大量讀取和弄臟的緩沖區,我會說您的時間將用於維護TOAST 表,刪除巨大的“值”列。 我不知道為什么以前沒有問題,也許你以前自然一次只刪除幾條記錄,或者你以前主要是刪除較小的記錄。 你說 50% 低於 50 字節,但也許那 50% 分布不均,你只是碰到了一大塊大塊。
至於select的速度,當你只有select“鍵”列時,它不需要訪問“值”列的TOAST記錄,所以它不會花任何時間這樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.