PostgreSQL Note
MVCC(Multiversion Concurrency Control)
MVCC(多版本並行控制)是 PostgreSQL 處理並發讀寫的核心機制。
每次寫入不會覆蓋舊資料,而是建立新版本,讀取時看到的是當下 transaction 開始時的資料快照,所以讀不會被寫擋住。
Python 類比:MVCC 就像 Python 的
copy-on-write。當你修改一個 list 時,舊的 snapshot 仍然存在,讀取舊 snapshot 的人不受影響。
Serial 欄位陷阱
如果 INSERT 時手動帶入 serial(auto-increment)欄位的值,自動計數器不會觸發,導致之後自動新增時出現衝突:
key(id=8) is exists.
解法: INSERT 時不要帶 serial 欄位,讓資料庫自動產生。如果已有衝突,手動重置 sequence:
SELECT setval('table_id_seq', (SELECT MAX(id) FROM table));
Multiprocessing 注意事項
在多進程環境下,不能共用資料庫連線:
TCP connections are represented as file descriptors, which usually work across process boundaries, meaning this will cause concurrent access to the file descriptor on behalf of two or more entirely independent Python interpreter states.
解法: 每個 process 建立自己的連線,用 process key 管理:
def init_process_db():
current_process = multiprocessing.current_process()
process_key = f"{current_process.name}_{current_process.pid}"
if process_key in _process_local:
return _process_local[process_key].get("db_manager")
db_manager = DatabaseManager(database_config=db_config)
_process_local[process_key] = {"db_manager": db_manager}
return db_manager
EXPLAIN ANALYZE:讀懂查詢計畫
EXPLAIN ANALYZE 是優化查詢的最重要工具。它不只告訴你「計畫怎麼跑」,還會實際執行並告訴你「實際花了多少時間」。
Python 類比:
EXPLAIN就像 Python 的cProfile加上line_profiler,讓你看到每一步實際的執行時間和資源消耗。
EXPLAIN ANALYZE
SELECT * FROM inference_results
WHERE model_name = 'gpt-4' AND created_at > NOW() - INTERVAL '7 days'
ORDER BY score DESC
LIMIT 100;
讀懂輸出
Limit (cost=1234.56..1235.06 rows=100 width=128) (actual time=45.123..45.234 rows=100 loops=1)
-> Sort (cost=1234.56..1237.81 rows=1300 width=128) (actual time=45.120..45.180 rows=100 loops=1)
Sort Key: score DESC
Sort Method: top-N heapsort Memory: 52kB
-> Index Scan using idx_results_model_created on inference_results
(cost=0.43..1198.12 rows=1300 width=128) (actual time=0.123..43.450 rows=1300 loops=1)
Index Cond: ((model_name = 'gpt-4') AND (created_at > (now() - '7 days'::interval)))
Planning Time: 0.5 ms
Execution Time: 45.3 ms
| 欄位 | 說明 | 注意點 |
|---|---|---|
cost=X..Y | 估計成本(相對單位,X=第一筆,Y=全部) | 和 actual 差很多代表統計資料過舊,執行 ANALYZE |
actual time=X..Y | 實際執行時間(ms,X=第一筆,Y=全部) | 這是最重要的數字 |
rows=N | 實際回傳的行數 | 和 cost 裡的 rows 差很多 → index 統計不準 |
loops=N | 這個節點執行幾次 | Nested Loop 裡 loops 很大 → 可能有 N+1 |
常見壞訊號
-- 壞:全表掃描,代表沒有用到 index
Seq Scan on inference_results (cost=0.00..98765.43 rows=1000000 ...)
-- 壞:Nested Loop 內層 rows 很大,典型 N+1
Nested Loop (... loops=10000)
-- 好:用了 Index
Index Scan using idx_results_model_created on inference_results
Bitmap Index Scan on idx_results_metadata (用於 JSONB GIN index)