1、問題描述
今天 QQ群裡在讨論一個問題,在某個環境裡面,需要修改單個表的多個字段,造成了資料混亂,跟理想修改的資料不一緻。
1.1 模拟問題現象
# 注意: 建立的表沒有主鍵,且 t1 表是 innodb 引擎
root@localhost [keme]>create table t1 (a int,b int,primary key(a));
Query OK, 0 rows affected (0.02 sec)
root@localhost [keme]>insert into t1 values (1,10),(2,20),(3,30);
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0
root@localhost [keme]>select * from t1;
+------+------+
| a | b |
+------+------+
| 1 | 10 |
| 2 | 20 |
| 3 | 30 |
+------+------+
# 進行修改多個字段
root@localhost [keme]>update t1 set a=a+1,b=a+10 where a=2;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
root@localhost [keme]>select * from t1;
+------+------+
| a | b |
+------+------+
| 1 | 10 |
| 3 | 13 |
| 3 | 30 |
+------+------+
3 rows in set (0.00 sec)

修改的條件是a=2,為啥修改的結果是(3,13),不應該是(3,12)嗎?
是不是很多人以為修改的結果是(3,12),是不是感覺資料是亂的,如果一條資料改回正常還是挺簡單,關鍵資料量很多改的就費勁了啊,時間還長。
1.2 問題故障原因
因為 update 是目前讀,讀取的是記錄資料的最新版本,就是
update t1 set a=a+1,b=a+10 where a=2;
update a=a+1 因為要做目前讀 現在a =2 +1 後 a=3 ,保證最新值
在做 b=a+10
也需要讀到a值的最新的值 還要加鎖, 現在a的值已經變成3 , 在加10不就變成13
1.3 解決故障
# 我先改回原來資料
root@localhost [keme]>update t1 set a=2,b=20 where a=3 and b=13;
# 可以把 a 列暫存在一個臨時變量裡
root@localhost [keme]>select a into @a from t1 where a=2;
# 修改資料
root@localhost [keme]>update t1 set a=@a+1,b=@a+10 where a=@a;
#驗證結果 是不是 (3,12)
2、問題總結
我們需要知道一些相關原理: 快照讀,目前讀
2.1、快照讀(select)
執行select的時候,innodb預設會執行快照讀,快照讀,也就是讀取快照的資料,資料雖然是一緻的,但是資料是曆史資料。
快照讀:隻是簡單的 select ,不包括 select ... lock in share mode, select ... for update
2.2、目前讀
select ... lock in share mode
select ... for update
insert
update
delete
當你執行這幾個操作的時候預設會執行目前讀,也就是會讀取最新的記錄,也就是别的事務送出的資料你也可以看到。
update 執行目前讀,然後把傳回的資料加鎖,之後執行update。
加鎖是防止别的事務在這個時候對這條記錄做什麼,預設加的是排他鎖,也就是隻允許讀,其他都不可以,這樣就可以保證資料不會出錯了。
3、延伸閱讀
上面的情況是:
- innodb 引擎
- 表中無主鍵
如果表中有主鍵,或者是其他引擎,正确操作,請看下面的大神文章
https://imysql.com/2008_06_17_sth_about_update_duplicate_key