簡體   English   中英

在行數少的未引用 PostgreSQL 表上更新和刪除速度非常慢

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM