「安全升級」翻車記:MySQL 混合式二進位日誌讓 AUTO_INCREMENT 複寫錯位
個人技術部落格(Hacker News 轉載) · 2026-08-30
一組工程團隊在替 MySQL 執行一次「照著官方文件走」的版本升級時,發現複寫(replication)悄悄把某張表的外鍵全部指錯了對象。問題根源不是升級本身,而是幾週前一次替既有表加上 AUTO_INCREMENT 欄位的 ALTER TABLE,搭配 MySQL 的混合式(MIXED)二進位日誌格式,在來源端(source)與複寫端(replica)產生了兩套不同的 ID 順序。
原本的問題
團隊在其中一張表(文中稱為表 X)加上一個 id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,並讓六張相依表改用這個新 ID 當外鍵。升級後檢查資料,發現五張表的外鍵值完全正確,唯獨第六張表對不上——來源端 ID 為 1 的資料列,在複寫端卻變成了 ID 為 26。受影響的只有一張表,其餘結構相同的表都沒事,這讓最初的排查方向完全走偏。
採用的方法
追查後發現關鍵差異在於二進位日誌的紀錄模式。MySQL 的 MIXED 格式預設用 STATEMENT 模式紀錄(把 SQL 陳述式送到複寫端重新執行),但在特定情況下會自動切換成 ROW 模式(把來源端算好的結果值直接寫入複寫端)。五張表的 UPDATE 都以 STATEMENT 模式重播,在兩端各自重新計算,自然拿到各自環境正確的 ID;唯獨第六張表因為牽涉 AUTO_INCREMENT 欄位,被 MySQL 判定為 STATEMENT 模式不安全,強制轉成 ROW 模式,直接把來源端算出來的 ID 複製過去。
根據 MySQL 8.0 Reference Manual,用 ALTER TABLE 幫既有表加上 AUTO_INCREMENT 欄位時,「來源與複寫端不一定會產生相同的資料列順序」,因為編號順序取決於儲存引擎與資料列處理順序。文件建議的安全作法是改建一張新表,用明確且涵蓋原表所有欄位的 ORDER BY 搭配 INSERT ... SELECT 重新產生順序一致的 ID,而不是直接對既有表下 ALTER TABLE ADD COLUMN。
CREATE TABLE t2 LIKE t1;
ALTER TABLE t2 ADD id INT AUTO_INCREMENT PRIMARY KEY;
INSERT INTO t2 SELECT * FROM t1 ORDER BY col1, col2;
DROP TABLE t1;
ALTER TABLE t2 RENAME t1;實際效果
因為當初加欄位沒有依照上述流程做,來源端與複寫端在替既有資料列編號時走了不同順序,兩端的 ID 從一開始就對不齊,只是在升級前的日常寫入流量下沒被踩到——直到升級觸發了一次涉及該表的批次 UPDATE,ROW 模式把來源端的 ID 值原封不動搬到複寫端,錯位才浮出水面,造成六張相依表中的一張外鍵全部指向錯誤的資料列。
- MIXED 格式下含 AUTO_INCREMENT 的陳述式會被標記為 STATEMENT 不安全,因而強制切換為 ROW 記錄
- ROW 模式記錄的是「結果值」而非「陳述式」,兩端資料順序若不同步,值會被誤植
- 官方建議的加欄位流程需要重建整張表並指定完整
ORDER BY,而非簡單的ALTER TABLE ADD COLUMN
原始來源:A safe MySQL upgrade that wasn't so safe、MySQL 8.0 Reference Manual: Replication and AUTO_INCREMENT、MySQL 8.0 Reference Manual: Binary Logging Formats
用二元關係重新定義關聯代數:UCLA 團隊推出查詢語言 Prela
Prela 官方教學(UCLA RePL Lab) · 2026-08-30
UCLA 資料庫研究團隊 RePL 發布了一份教學,示範如何用不到 20 行 Python 程式碼實作出一套具備 SQL 核心表達力的查詢引擎,背後的語言叫 Prela。教學重點不是效能,而是證明關聯式查詢可以完全建立在「二元關係」(binary relation,只有兩個欄位的表)之上,搭配少數幾個組合運算子,就能表達傳統上需要 JOIN、WHERE、GROUP BY 才寫得出的查詢。這套理論同時發表於論文 arXiv:2607.26356。
背景
Prela 的理論基礎不是 Codd 在 1970 年提出的關聯代數,而是更早的 Tarski 關係代數(Tarski's Algebra of Relations, TAR)。論文作者 Yisu Remy Wang 與 Paul Talma 在 arXiv:2607.26356 中指出,TAR 比關聯代數早了超過一百年提出,卻長期沒被當作資料庫系統的實作基礎。他們主張 TAR 對現代系統提供更好的抽象層級,並用 Prela 證明這套理論可以落地。
規格細節
教學裡的最小可行引擎只用兩個函式就撐起整個查詢組合機制:select 把一個關係第二欄位的值,透過字典查找換成另一個關係對應的值;where 則用同樣的字典查找做過濾,只保留第二欄位存在於另一個關係鍵值中的資料列。
def select(r, s):
d = dict(s)
return [(x, d[y]) for x, y in r if y in d]
def where(r, s):
d = dict(s)
return [(x, y) for x, y in r if y in d]查詢寫法用 & 運算子把多個條件串接成單一運算式,例如教學中示範「找出 1942 年上映的美國電影」:movie.where(company.s(country).eq("[us]")).select(title & year.eq(1942))。每個運算元都是二元關係,複合查詢就是關係之間的連續組合,而不是像 SQL 那樣把所有條件塞進一句陳述式裡。
影響範圍
目前 Prela 仍是研究性質的教學實作,原始碼放在 GitHub 上一個單檔 Python 腳本,並附帶線上互動環境 Snip,可以直接在瀏覽器裡跑教學裡的範例。
- 資料模型只有二元關係,多欄位表格需要先分解成多個「列號 → 值」的對應
- 組合運算子取代巢狀 SQL 子句,查詢用鏈式呼叫寫成單一運算式
- 核心引擎的可執行語意不到 15 行 Python,對照的是傳統 SQL 引擎動輒數萬行的剖析器與最佳化器
原始來源:Prela Tutorial、arXiv:2607.26356 - Revisiting the Algebraic Foundation of Relational Data、GitHub: remysucre/prela