1.簡介
MySQL的隔離性是由鎖機制來保證的。鎖是計算機協調多個進程或線程并發地訪問某一資源你的機制。當多線程并發地訪問某個數據時,尤其是在涉及金錢等安全敏感性數據的時候,需要保證數據在任意時刻最多只有一個線程可以對其進行修改,從而保證數據的一致性和完整性。
2.MySQL并發事務訪問相同記錄
并發事務訪問相同記錄可以大致分為三種情況:
2.1 讀-讀情況
讀-讀情況就是多個事務同時讀取某條或多條相同的記錄,由于讀操作本身不會改變數據,因此是不存在任何數據安全問題的。
2.2 寫-寫情況
寫-寫情況即并發事務相繼對相同的記錄進行修改,在這種情況下會產生臟寫的問題,任何一種隔離級別都不允許這種情況的產生,因此多個未提交的事務對同一條記錄進行修改時,會讓它們排隊去執行,這個排隊操作是通過鎖來保證。這個所謂的鎖其實是一個內存中的結構,在事務執行前本來是沒有鎖的,也就是說一開始是沒有鎖結構和記錄進行關聯的,如圖所示:
當一個事物想要對這條記錄進行改動的時候,首先會看看內存中有沒有與這條記錄關聯的表結構,當沒有的時候就會在內存中生成一個鎖結構與之關聯。比如事務T1需要對記錄進行修改,就需要生成一個索結構與之關聯,如下圖:
在鎖結構里有很多信息,為了簡化理解,就拿兩個比較重要的字段展示:
- trx信息:代表這個鎖結構是哪個事務生成的。
- is waiting:表示當前事務是否在等待
當T1改動這條記錄后,就會生成一個索結構與對應的記錄進行關聯,由于之前沒有別的事務對該記錄進行修改,所以is waiting是false。我們把這個場景稱之為獲取鎖成功,之后就可以進行后續操作了。
在T1事務對該記錄進行提交之前,事務T2也想對該記錄進行修改,那么會先看看有沒有鎖結構與該記錄進行關聯,發現存在鎖結構與該記錄關聯,那么T2會再生成一個自己的索結構與該記錄進行關聯,不過此時的is wating則為true,因為事務T2需要等待事務T1提交后才能對該記錄進行修改。這個場景我們就稱之為獲取鎖失敗。如下圖:
在事務T1提交后,會將與該記錄關聯的鎖結構釋放掉,然后查看是否有其他線程等待獲取鎖,發現T2事務在等待獲取鎖,會將T2鎖結構的is wating改為false,且喚醒T2事務對應的線程,然后T2事務就可以正常執行了。如下圖:
2.3 讀-寫情況
讀-寫或者寫-讀的情況,即一個事物在讀取某條記錄的時候,其他事務在對該記錄進行修改,那么可能會產生 臟讀、不可重復讀、幻讀的問題。MySQL在RR級別就解決了幻讀的問題。
2.4 并發問題的讀解決方案
怎么解決臟讀、不可重復讀、幻讀的問題呢?有兩種解決方案。
- 方案一:讀操作使用MVCC,寫操作加鎖
所謂MVCC就是多版本并發控制,當進行常規的select操作(即簡單的select 語句,不帶for update或者lock in share mode)的時候,這時是快照讀。此時在開啟事務的時候會維護一個當前活躍事務(就是已開啟但是還未提交的事務)列表-trx_list,里面的記錄了當前活躍事務的id,由于事務的id是隨著開啟時間遞增的,所以在最小事務id之前的事務都已經提交了,這些事務的id對于當前事務來說自然是可見的。最大事務id則是系統將要分配的下一個事務id,所以在最大事務id之后的事務對于當前事務來說都是未開啟的,這些事務所做的修改對于當前事務來說自然是不可見的。同時記錄在undo log中維護了各個事務對其修改的歷史版本,所以根據事務id之間的關系就可以判斷當前事務應該看到該記錄的哪個事務版本的數據。由于當前讀在開啟事務的時候會生成一個ReadView,其中維護了上述的trx_list,且在本事務中多次進行查詢都是用的最初的ReadView,因此在本事務中不論讀多少次,某個記錄的值都不會變,這就解決了臟讀、不可重復讀的問題。