MySQL 事務的基本概念
MySQL 事務主要用於處理操作量大,複雜度高的數據。比如說,在人員管理系統中,你刪除一個人員,你即需要刪除人員的基本資料,也要刪除和該人員相關的信息,如信箱,文章等等,這樣,這些數據庫操作語句就構成一個事務!
- 在 MySQL 中只有使用了 Innodb 數據庫引擎的數據庫或表才支持事務。
- 事務處理可以用來維護數據庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行。
- 事務用來管理 insert,update,delete 語句
其存在的意義是爲了保證即使在併發情況下也能正確的執行crud操作。
mysql事務:
併發事務處理帶來的問題
相對於串行處理來說,併發事務處理能大大增加數據庫資源的利用率,提高數據庫系統的事務吞吐量,從而可以支持更多的用戶。但併發事務處理也會帶來一些問題,主要包括以下幾種情況。
1.更新丟失(Lost Update):
當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最後的更新覆蓋了由其他事務所做的更新。例如,兩個編輯人員製作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。
2.髒讀(Dirty Reads):
一個事物讀取到另一事物未提交數據。一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“髒”數據,並據此做進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫做"髒讀"。
3.不可重複讀(Non-Repeatable Reads):
一個事物讀取到另一事物已提交的數據。一個事務在讀取某些數據後的某個時間,再次讀取以前讀過的數據,卻發現其讀出的數據已經發生了改變!這種現象就叫做“不可重複讀”。
不可重複讀的重點是修改:
同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了
4.幻讀(Phantom Reads):
一個事物讀取到另一事物已提交的數據。一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱爲“幻讀”。
幻讀的重點在於新增或者刪除:
同樣的條件, 第1次和第2次讀出來的記錄數不一樣
注意:
幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。
從總的結果來看, 似乎不可重複讀和幻讀都表現爲兩次讀取的結果不一致。但如果你從控制的角度來看, 兩者的區別就比較大。
對於前者, 只需要鎖住滿足條件的記錄。
對於後者, 要鎖住滿足條件及其相近的記錄。
思考:如何解決幻讀(待續?)
ACID的定義:
Atomic原子性: 一個事務的所有系列操作步驟被看成是一個動作,所有的步驟要麼全部完成要麼一個也不會完成,如果事務過程中任何一點失敗,將要被改變的數據庫記錄就不會被真正被改變。
Consistent一致性:一致性是指事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態,也就是說一個事務執行之前和執行之後都必須處於一致性狀態。拿轉賬來說,假設用戶A和用戶B兩者的錢加起來一共是5000,那麼不管A和B之間如何轉賬,轉幾次賬,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。
Isolated隔離性: 主要用於實現併發控制, 隔離能夠確保併發執行的事務能夠順序一個接一個執行,通過隔離,一個未完成事務不會影響另外一個未完成事務。
Durable持久性: 一旦一個事務被提交,它應該持久保存,不會因爲和其他操作衝突而取消這個事務。很多人認爲這意味着事務是持久在磁盤上,但是規範沒有特別定義這點。
數據庫的四種隔離級別
在高併發的情況下,要完全保證其ACID特性是非常困難的,除非把所有的事物串行化執行,但帶來的負面的影響將是性能大打折扣。很多時候我們有些業務對事物的要求是不一樣的,所以數據庫中設計了四種隔離級別,供用戶基於業務進行選擇。
主流數據庫的默認隔離級別:
Oracle中默認級別是 Read committed
mysql 中默認級別 Repeatable read。
查看mysql 的默認隔離級別命令:SELECT @@tx_isolation
mysql鎖
鎖的類型
共享鎖與排它鎖
Innodb存儲引擎實現瞭如下2種標準的行級鎖:
共享鎖(S lock),允許事務讀取一行數據。
排它鎖(X lock),允許事務刪除或者更新一行數據。
當一個事務獲取了行r的共享鎖,那麼另外一個事務也可以立即獲取行r的共享鎖,因爲讀取並未改變行r的數據,這種情況就是鎖兼容。但是如果有事務想獲得行r的排它鎖,則它必須等待事務釋放行r上的共享鎖。
簡單的說,就是一個事務鎖住了某行,其他事務可以讀取該行,但不能做修改和刪除等操作。
Innodb存儲引擎支持多粒度鎖定,這種鎖定允許在行級別上的鎖和表級別上的鎖同時存在。爲了支持在不同粒度上進行加鎖操作,InnoDB存儲引擎支持一種額外的鎖方式,就是意向鎖。
意向鎖是表級別的鎖,其設計目的主要是爲了在一個事務中揭示下一行將被請求的鎖的類型。它也分爲兩種:
意向共享鎖(IS Lock),事務想要獲得一個表中某幾行的共享鎖。
意向排它鎖(IX Lock),事務想要獲得一個表中某幾行的排它鎖。
行鎖的三種算法
record lock,記錄鎖,單個行記錄的鎖
Gap Lock,間隙鎖,鎖定一個範圍,但是不包含當前行
Next_key Lock:鎖定一個範圍,同時包含當前行,即包含了記錄鎖和間隙鎖,InnoDB默認加鎖方式是Next_key Lock
注意:InnoDB行鎖是通過給索引上的索引項加鎖來實現的,只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖
間隙鎖
什麼是間隙鎖?
間隙鎖是一個在索引記錄之間的間隙上的鎖。
間隙的範圍
根據檢索條件向下尋找最靠近檢索條件的記錄值A作爲左區間,向上尋找最靠近檢索條件的記錄值B作爲右區間,即鎖定的間隙爲(A,B)。
select * from t where number=6;那麼間隙鎖鎖定的間隙爲:(5,11),所以你再想插入5到11之間的數就會被阻塞。
間隙鎖的作用
保證某個間隙內的數據在鎖定情況下不會發生任何變化。比如我mysql默認隔離級別下的可重複讀(RR)。
注意:當使用唯一索引來搜索唯一行的語句時,間隙鎖會降級爲記錄鎖。
數據庫死鎖
AB-BA死鎖
意向共享鎖引起的死鎖
@see 《MySQL技術內幕:InnoDB存儲引擎(第2版)》
常見案例參考
用戶A查詢一條紀錄,然後修改該條紀錄;這時用戶B先查詢這條記錄然後修改該條紀錄,這時用戶A的事務裏鎖的性質由查詢的共享鎖企圖上升到獨佔鎖,
而用戶B裏的試圖修改上升獨佔鎖由於A 有共享鎖存在所以必須等A釋放掉共享鎖,而A由於B的共享鎖而無法上升獨佔鎖A也就不可能釋放共享鎖,於是出現了死鎖。
@see https://www.cnblogs.com/sivkun/p/7518540.html
https://blog.csdn.net/zheng0518/article/details/53844720
解決辦法:
數據庫層面:
使用排它鎖如:select…for update,
代碼層面:
樂觀鎖機制,使用版本號控制。
MVCC
MVCC的含義
MVCC (Multiversion Concurrency Control),即多版本併發控制技術,它使得大部分支持行鎖的事務引擎,不再單純的使用行鎖來進行數據庫的併發控制,取而代之的是把數據庫的行鎖與行的多個版本結合起來,只需要很小的開銷,就可以實現非鎖定讀,從而大大提高數據庫系統的併發性能
自己理解:就是用版本號來替代鎖,已達到併發下的讀寫。
Mysql中的MVCC
爲了支持事務,Innbodb引入了下面幾個概念:
redo log
redo log就是保存執行的SQL語句到一個指定的Log文件,當Mysql執行recovery時重新執行redo log記錄的SQL操作即可。當客戶端執行每條SQL(更新語句)時,redo log會被首先寫入log buffer;當客戶端執行COMMIT命令時,log buffer中的內容會被視情況刷新到磁盤。redo log在磁盤上作爲一個獨立的文件存在,即Innodb的log文件。
undo log
與redo log相反,undo log是爲回滾而用,具體內容就是copy事務前的數據庫內容(行)到undo buffer,在適合的時間把undo buffer中的內容刷新到磁盤。undo buffer與redo buffer一樣,也是環形緩衝,但當緩衝滿的時候,undo buffer中的內容會也會被刷新到磁盤;與redo log不同的是,磁盤上不存在單獨的undo log文件,所有的undo log均存放在主ibd數據文件中(表空間),即使客戶端設置了每表一個數據文件也是如此。
rollback segment
回滾段這個概念來自Oracle的事物模型,在Innodb中,undo log被劃分爲多個段,具體某行的undo log就保存在某個段中,稱爲回滾段。可以認爲undo log和回滾段是同一意思。
實現原理:
begin->用排他鎖鎖定該行->記錄redo log->記錄undo log->修改當前行的值,寫事務編號,回滾指針指向undo log中的修改前的行->事務提交或者回滾
上述過程確切地說是描述了UPDATE的事務過程,其實undo log分insert和update undo log,因爲insert時,原始的數據並不存在,所以回滾時把insert undo log丟棄即可,而update undo log則必須遵守上述過程
下面分別以select、delete、 insert、 update語句來說明
SELECT
Innodb檢查每行數據,確保他們符合兩個標準:
1、InnoDB只查找版本早於當前事務版本的數據行(也就是數據行的版本必須小於等於事務的版本),這確保當前事務讀取的行都是事務之前已經存在的,或者是由當前事務創建或修改的行
2、行的刪除操作的版本一定是未定義的或者大於當前事務的版本號,確定了當前事務開始之前,行沒有被刪除
符合了以上兩點則返回查詢結果。
INSERT
InnoDB爲每個新增行記錄當前系統版本號作爲創建ID。
DELETE
InnoDB爲每個刪除行的記錄當前系統版本號作爲行的刪除ID。
UPDATE
InnoDB複製了一行。這個新行的版本號使用了系統版本號。它也把系統版本號作爲了刪除行的版本。
說明
insert操作時 “創建時間”=DB_ROW_ID,這時,“刪除時間 ”是未定義的;
update時,複製新增行的“創建時間”=DB_ROW_ID,刪除時間未定義,舊數據行“創建時間”不變,刪除時間=該事務的DB_ROW_ID;
delete操作,相應數據行的“創建時間”不變,刪除時間=該事務的DB_ROW_ID;
select操作對兩者都不修改,只讀相應的數據
@see
Mysql中的MVCC https://blog.csdn.net/chen77716/article/details/6742128
【mysql】關於innodb中MVCC的一些理解 http://www.cnblogs.com/chenpingzhao/p/5065316.html
思考
1.高併發下,如何做到安全的修改同一行數據。
1.樂觀鎖,通過版本號比較
2.悲觀鎖
1)業務層加鎖
2)數據庫層加鎖,如使用select…for update或lock in share mode。
參考文獻:
1.MySQL事務(Transaction)及其ACID屬性、併發事務處理帶來的問題講解
https://www.2cto.com/database/201803/726901.html
2.《MySQL技術內幕:InnoDB存儲引擎(第2版)》