並發操作會帶來一系列的問題
-
更新丟失(lost update)
當兩個或多個事務選擇了同一行然后基於最初選定的值更新改行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新的問題,最后更新覆蓋了由其他事務所做的更新
-
臟讀 (Dirty reads)
一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的數據就處於不一致的狀態;這時,另一個事務也讀取同一條基礎,如果不加控制,第二個事務讀取這些"臟"數據,並據此作進一步的處理,就會產生未提交的數據的依賴關系,這種叫做"臟讀"
-
不可重復讀(Non-Repeatable reads)
一個事務在讀取某些數據的某個時間,再次讀取以前讀過的數據,卻發現其讀出的數據已經發生改變,或者某些記錄被刪除了!! 這就叫不可重復讀
(不符合隔離性)
-
幻讀
一個事務按相同的查詢條件讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象叫做"幻讀",好像重來沒出現過
設置了 mysql 事務隔離級別, mysql對於操作同一行數據會自動加鎖
對於可重復讀, mysql使用的是 MVCC 機制 ,multi version concurrent control,多版本並發控制機制
select 操作不會更新版本,每次查詢都會做一次快照,用的是快照版本。然后修改后,真實數據以及和快照版本不一致了,但是 mysql為了讓邏輯准確,更新的時候會那數據庫最新的數據進行更新,而讀是讀的快照版本的數據。
商品超賣,就是這種原因。
https://blog.csdn.net/huaishu/article/details/89924250
這種就是當前讀和快照讀的區別
快照讀會讓性能高一點。所以 select 出來再 update 會有 bug
注意點:sql應該這樣寫: update account set balance = balance-50 where id = 1;
可重復讀的話,select不會更新版本好,是快照讀(歷史版本),而insert 、update和delete 會更新版本好,是當前讀(當前版本)
所以更新之后再開查就可能會有幻讀的現象:
session1: select * from account;
session2: insert into account (name,money) values('ss',100);
session1: update account set name = 'lyr' where id = 1 ;
session1: select * from account;
如果 session1: 沒有update account 這張表,會用快照讀,不會出現幻讀的問題
然而: session1: update 了 account這張表,那么就會從快照讀改當前讀
而session2 這個時候 插入了一條 name='ss', money=100 的數據
session1 再來查表,發現無緣無故有多了一條,這個就像幻覺一樣的數據(幽靈般的出現了)
這個就是幻讀
改成 串行化,那就什么問題也沒有了,但是不應該這樣,因為效率低
mysql 使用可重復讀 兼顧了效率盡量的解決了臟讀和不可重復讀的問題
查看mysql近期死鎖的日志信息:
show engine innodb status\G
mysql的優化:
- 盡可能讓所有數據檢索通過索引完成,避免無索引行鎖升級為表鎖
- 合理設計索引,盡量縮小鎖的范圍
- 盡可能檢索檢索條件,避免間隙鎖
- 盡量控制事務大小,減少鎖定資源量和時間長度
- 盡可能低級別的事務隔離
這樣就可以解決並發問題了
- update account set money = money-1 where id='llyr' and money >0;
mysql可以使用一個間隙鎖,只要是范圍的話 mysql自動加鎖,一般不會有幻讀問題
條件 是一個范圍,mysql就加鎖了
這樣可以解決幻讀問題
https://blog.csdn.net/spring_model/article/details/53992450
還有其他的鎖,比如 for update 獨占鎖之類的
mysql 開啟事務的時候 update語句的時候加鎖,COMMIT后釋放
所以sql 寫的好,一般不會有問題。