現象複現
CREATE TABLE test(id INT, tag INT, num INT);
INSERT INTO test VALUES (1,1,1), (2,2,2), (3,3,3);
-- 測試 1:tag 在前,num 在後
UPDATE test SET tag = 4, num = CASE WHEN tag = 4 THEN 4 ELSE 3 END WHERE tag = 3;
-- MySQL 結果:tag=4, num=4 (因 tag 已更新為 4,CASE 判斷為真)
-- SQL Server 結果:tag=4, num=3 (依據更新前快照,tag=3,CASE 判斷為假)
-- 測試 2:調換順序,num 在前,tag 在後
UPDATE test SET num = CASE WHEN tag = 4 THEN 4 ELSE 3 END, tag = 4 WHERE tag = 3;
-- MySQL 結果:tag=4, num=3 (num 計算時 tag 仍為 3)
-- SQL Server 結果:tag=4, num=3 (快照機制,順序無影響)
核心差異對照表
| 特性 | MySQL (InnoDB) | SQL Server |
| SET 子句評估順序 | 左至右,後欄位可見前欄位已更新值 | 全欄位基於 更新前快照,順序無關 |
| CASE/函式內引用同表欄位 | 讀到「已更新」的值 | 讀到「原始」的值 |
| 標準相容性 | 不符合 SQL:2003 標準 | 符合標準行為 |
避坑指南
- 不要在同一 UPDATE 中「先改欄位 A,再用 A 計算欄位 B」 —— MySQL 會產生非預期結果。
- 若需依「舊值」計算新值,請改用子查詢或暫存變數:
UPDATE test t
JOIN (SELECT id, CASE WHEN tag = 3 THEN 4 ELSE 3 END AS new_num FROM test WHERE tag = 3) src
ON t.id = src.id
SET t.tag = 4, t.num = src.new_num;
- 或分兩步執行:先
UPDATE ... SET num = ... WHERE tag = 3; 再 UPDATE ... SET tag = 4 WHERE tag = 3;。
- 程式碼審查時,特別檢查
SET a = ..., b = f(a) ... 這類模式。
延伸閱讀