PG 的寫入放大來自位置過期,不是欄位變動

直覺會說:「我只改了電話,那只有『電話』那個索引要更新吧?」——在 PostgreSQL 這是錯的,而錯的理由不在欄位,在位置。

索引的每一列都是兩段

索引的一列 =  [ 被索引的欄位值 ]  →  [ 資料在哪裡(CTID) ]
                  左半邊                    右半邊
  • 左半邊確實只有被改到的那個索引要動
  • 右半邊記的是「這筆資料在 Heap 的哪個位置」——每一個索引都記

PG 的更新是搬家,不是塗改

PG 的 MVCC 把 UPDATE 視為插入一筆新 Tuple、把舊的標記成過期。資料換了新位置 → 新的 CTID → 所有索引的右半邊全部過期。

表上有 10 個索引,你只改了一個跟誰都無關的欄位,這 10 個索引全部都要改寫一次。 這就是寫入放大(Write Amplification),也是巨大 I/O 壓力的來源。

「這個欄位跟那個索引無關」保護不了那個索引,因為索引記的不只是欄位,還有資料在哪。

MySQL 為什麼不會中

InnoDB 是原地更新(In-place Update):資料沒有搬家,舊版本丟去 Undo Log 做版本控制。位置不變 → 右半邊不用動 → 只要索引欄位沒變,二級索引完全不需要修改。

更新方式要改幾個索引
MySQL原地塗改 + Undo Log只有左半邊被改到的那些
PostgreSQL搬到新位置(Append-only)全部(除非 HOT 生效)

推論:所以這兩種表的選型是相反的

  • 更新頻繁 + 表上索引多 → MySQL,原地更新避開這場災難
  • 寫入以 INSERT 為主、少 UPDATE(日誌型資料) → PG 的 Append-only 非常快

例外

PG 的 HOT(Heap Only Tuple) 可以讓新版本留在同一頁、索引不用碰——但「仍有限制」,而限制是什麼沒人說清楚 → PG 的 HOT 什麼時候會失效?

後續帳單(死 Tuple 要有人清):舊版本放哪裡,決定了誰會膨脹 前置:MySQL 把資料放在主鍵索引的葉子,PostgreSQL 放在獨立的 Heap