Postgresql 極慢計數(帶索引,簡單查詢)
我需要在具有數百萬行的表上執行這些簡單的查詢:
SELECT COUNT(*) FROM "subscriptions" WHERE "subscriptions"."project_id" = 123;
SELECT COUNT(*) FROM "subscriptions" WHERE "subscriptions"."project_id" = 123 AND "subscriptions"."trashed_at" IS NULL;
對於項目 123,這兩個查詢的計數結果約為 5M。
我有一個索引
project_id
,還有另一個索引(project_id, trashed_at)
:"index_subscriptions_on_project_id_and_created_at" btree (project_id, created_at DESC) "index_subscriptions_on_project_id_and_trashed_at" btree (project_id, trashed_at DESC)
問題是這兩個查詢都非常慢,每個查詢大約需要 17 秒。
這些是結果
EXPLAIN ANALIZE
:QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate (cost=2068127.29..2068127.30 rows=1 width=0) (actual time=17342.420..17342.420 rows=1 loops=1) -> Bitmap Heap Scan on subscriptions (cost=199573.94..2055635.23 rows=4996823 width=0) (actual time=1666.409..16855.610 rows=4994254 loops=1) Recheck Cond: (project_id = 123) Rows Removed by Index Recheck: 23746378 Heap Blocks: exact=131205 lossy=1480411 -> Bitmap Index Scan on index_subscriptions_on_project_id_and_trashed_at (cost=0.00..198324.74 rows=4996823 width=0) (actual time=1582.717..1582.717 rows=4994877 loops=1) Index Cond: (project_id = 123) Planning time: 0.090 ms Execution time: 17344.182 ms (9 rows)
QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate (cost=2047881.69..2047881.70 rows=1 width=0) (actual time=17557.218..17557.218 rows=1 loops=1) -> Bitmap Heap Scan on subscriptions (cost=187953.70..2036810.19 rows=4428599 width=0) (actual time=1644.966..17078.378 rows=4994130 loops=1) Recheck Cond: ((project_id = 123) AND (trashed_at IS NULL)) Rows Removed by Index Recheck: 23746273 Heap Blocks: exact=131144 lossy=1480409 -> Bitmap Index Scan on index_subscriptions_on_project_id_and_trashed_at (cost=0.00..186846.55 rows=4428599 width=0) (actual time=1566.163..1566.163 rows=4994749 loops=1) Index Cond: ((project_id = 123) AND (trashed_at IS NULL)) Planning time: 0.084 ms Execution time: 17558.522 ms (9 rows)
問題是什麼?
我可以做些什麼來提高性能(即在幾秒鐘內計數)?
獲取和計算 500 萬行是一項緩慢的業務。
有兩個問題:
- 點陣圖堆掃描花費的時間比必要的要長,因為
work_mem
它太小以至於不能包含每個表行一個位的點陣圖。然後它會降級為每 8KB 塊儲存一個位,這會導致在點陣圖堆掃描階段進行更多的堆提取以排除誤報。
所以提高
work_mem
會加快執行速度,但根據你的意見,不會很多。
- 與其查詢表本身,這很慢,PostgreSQL 還可以只讀取其中一個索引,因為它們包含所有需要的數據。
那會快得多,那麼為什麼 PostgreSQL 不這樣做呢?
解釋是 PostgreSQL 表和索引包含可見的行版本(元組)和不可見的元組(因為它們已被刪除或更新但尚未清理)。PostgreSQL 多版本實現需要這些“死元組”。
現在,哪些元組可見,哪些不可見的資訊僅儲存在表(堆)中,而不儲存在索引中。所以 PostgreSQL 必須查詢堆來確定哪些元組要計數,哪些不計數。
如果你
VACUUM
在表上執行,PostgreSQL 將更新一個名為“可見性映射”的好東西,它每 8KB 塊包含一個位,指示塊中的所有元組是否**對每個人都是可見的。如果可見性映射有一個塊是全部可見的,則索引掃描不必查詢表來確定它是否可以看到塊中的元組。如果這適用於大多數元組,那麼您將獲得一個僅索引掃描,這顯然要快得多。
所以你應該看到桌子經常被吸塵。
為此,讓 autovacuum 在此表上執行得更快、更頻繁:
ALTER TABLE subscriptions SET ( autovacuum_vacuum_scale_factor = 0.05, autovacuum_vacuum_cost_delay = 2 );
如果你有一個索引,你會更快
project_id
。
TL;DR 我已經通過執行以下命令解決了:
vacuum analyze subscriptions;
在該命令之後,查詢只需要 ~1s 而不是 ~17s。有關詳細說明,請參閱@Laurenz answer。
現在我使用以下設置更頻繁地執行 autovacuum
postgresql.conf
:autovacuum_vacuum_scale_factor = 0.01 autovacuum_analyze_scale_factor = 0.01
更新:隨著表的大小增加,上述設置是不夠的(因為隨著表的增長,真空變得不那麼頻繁)。對於非常大的表(即 10M+ 記錄),我使用了這個:
ALTER TABLE subscriptions SET (autovacuum_vacuum_scale_factor = 0, autovacuum_analyze_scale_factor = 0, autovacuum_vacuum_threshold = 10000, autovacuum_analyze_threshold = 10000);