在日常開發與運維中,MySQL 的事務隔離、併發更新、大數據分頁等問題屢見不鮮。本文結合實際場景,剖析三個典型坑點的成因與解法,助你寫出更健壯的 SQL。
幻讀指:事務 A 多次執行相同範圍查詢,事務 B 在中間插入/刪除滿足條件的行並提交,導致事務 A 兩次結果集行數不一致。MySQL 預設隔離級別 REPEATABLE READ(RR)雖通過 Next-Key Lock(記錄鎖+間隙鎖)在加鎖讀(SELECT ... FOR UPDATE / LOCK IN SHARE MODE)下防止幻讀,但快照讀(普通 SELECT)無法阻擋幻讀。
-- 事務 1 BEGIN; SELECT * FROM t; -- 返回 10 行 -- 事務 2 INSERT INTO t SELECT ...; COMMIT; -- 新增 5 行 -- 事務 1 再次查詢 SELECT * FROM t; -- 仍返回 10 行(MVCC 快照) -- 但若嘗試插入相同主鍵 INSERT INTO t SELECT ...; -- Duplicate key error!這就是幻讀的體現
SELECT ... FOR UPDATE 或 LOCK IN SHARE MODE 加鎖讀;-- 線程 A SELECT balance FROM account WHERE id=1; -- 讀到 100 -- 線程 B SELECT balance FROM account WHERE id=1; -- 讀到 100 -- 線程 A UPDATE account SET balance=100+50 WHERE id=1; -- 寫入 150 -- 線程 B UPDATE account SET balance=100+30 WHERE id=1; -- 覆蓋寫入 130,線程 A 的 +50 丟失
UPDATE account SET balance = balance + 50 WHERE id = 1; -- InnoDB 自動加行鎖(X 鎖),併發安全,無需應用層加鎖
原則:能用單條 UPDATE 解決的,絕不拆成 SELECT + UPDATE。
MySQL 必須掃描前 100,000 行並丟棄,再取 10 行。偏移量越大,掃描成本線性增長,本質是全表掃描。
| 方案 | 適用場景 | 核心思路 |
|---|---|---|
| 延遲關聯 | 有唯一遞增鍵(id) | SELECT * FROM t WHERE id > (SELECT id FROM t ORDER BY id LIMIT 100000,1) ORDER BY id LIMIT 10 |
| 書籤/游標分頁 | 前端配合,不需跳頁 | 記住上一頁最大 id,WHERE id > last_id ORDER BY id LIMIT 10 |
| 覆蓋索引優化 | 僅查索引列 | 建立覆蓋索引,EXPLAIN 出現 Using index,避免回表 |
幻讀源於快照讀無鎖,更新丟失源於非原子讀改寫,大偏移分頁源於全掃機制。理解底層原理,才能在業務代碼中寫出經得起併發考驗的 SQL。