PostgreSQL 效能優化(下):用 Array + GIN 把標籤變成索引
若你還沒讀過前兩集的問題定位與 EXPLAIN 拆解:
從觀察到動手:真正要換掉的是存取路徑
中集確認的事實是:LIKE 與 Regex 都讓 PostgreSQL 規劃出全表 Seq Scan,因為 B-tree 索引在 '%xxx%' 條件下找不到入口。要根本改變查詢成本,得做兩件事:改資料模型讓查詢語意能對應到合適的索引型態,建索引讓 PostgreSQL 有索引可走。
實作方案:把標籤查詢改成索引友善路徑
我把調校拆成兩步,簡單直接:先改資料型態,再改查詢寫法讓它吃到索引。
A. 資料模型調整(字串 -> 陣列)
先把 'tag1;tag2' 轉為 PostgreSQL 原生 text[],讓查詢語意可以直接映射到陣列運算子:
ALTER TABLE customers ADD COLUMN tags_array text[];
UPDATE customers
SET tags_array = string_to_array(nullif(trim(tags), ''), ';');
B. 建立 GIN 倒排索引
CREATE INDEX CONCURRENTLY idx_customers_tags_gin
ON customers USING gin (tags_array);
查詢改寫為陣列運算(如 &&, @>),讓 PostgreSQL 能走 GIN:
tags_array && ARRAY['tag_VIP']::text[]:判斷是否有交集tags_array @> ARRAY['tag_VIP']::text[]:判斷是否完整包含
可帶走的觀念:之後遇到 DB 效能問題都還用得上的基本功
下面這幾個基本功,下次遇到任何資料庫效能問題都會再用到:
- 量測優先於直覺:靠
EXPLAIN ANALYZE看計畫,永遠比靠經驗或感覺猜瓶頸可靠。 - 用複雜度語言描述現況:把
N、M、K這些變數寫出來,才有辦法理性比較不同寫法的代價。 - 資料模型是性能的天花板:SQL 語法只能在現有結構上做局部優化,要根本拉開差距常常得回頭改欄位設計。
- 優化要在 plan 上驗證:要看到
Seq Scan真的換成Index Scan才算數,執行時間變短不代表存取路徑改了。
結論
Regex 的確能先把 CPU 壓力降下來,但資料量一大,真正拉開差距的還是
Array + GIN這種直接改變查詢路徑的做法。
回頭快速複習前兩集: