mysql行鎖+可重複讀+讀提交
行鎖
-
innodb支援行鎖,myisam只支援表鎖,同一時刻每張表只能有一條資料被更新
-
在InnoDB事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放, 而是要等到事務結束時才釋放。這個就是兩階段鎖協議。
-
如果你的事務中需要鎖多個行,要把最可能造成鎖衝突、最可能影響並 發度的鎖的申請時機盡量往後放。
-
例子:假設你負責實現一個電影票線上交易業務,顧客A要在影院B購買電影票。我們簡化一點,這個業務需要涉及到以下操作:
-
從顧客A賬戶餘額中扣除電影票價;
-
給影院B的賬戶餘額增加這張電影票價;
-
記錄一條交易日誌。
-
也就是說,要完成這個交易,我們需要update兩條記錄,並insert一條記錄。當然,為了保證交易的原子性,我們要把這三個操作放在一個事務中。那麼,你會怎樣安排這三個語句在事務中的順序呢? 試想如果同時有另外一個顧客C要在影院B買票,那麼這兩個事務衝突的部分就是語句2了。因為它們要更新同一個影院賬戶的餘額,需要修改同一行資料。根據兩階段鎖協議,不論你怎樣安排語句順序,所有的操作需要的行鎖都是在事務提交的時候才
-
-
死鎖:事務A和事務B在互相等待對方的資源釋放,就是進入了死鎖狀態。
-
一種策略是,直接進入等待,直到超時。這個超時時間可以通過引數 innodb_lock_wait_timeout來設定。
-
預設是50s,在正常生產環境中是不可接受的
-
設定時間過短可能會誤傷很多,比如簡單的鎖等待
-
-
另一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將引數innodb_deadlock_detect設定為on,表示開啟這個邏輯。
-
如果有1000條併發更新同一行,那麼會有1000*1000併發量死鎖檢測,導致cpu上升
-
如果確定不會出現死鎖,可以關閉死鎖檢測。但是這種操作本身帶有一定的風險,因為業務設計的時候一般不會把死鎖當做一個嚴重錯誤,畢竟出現死鎖了,就回滾,然後通過業務重試一般就沒問題了,這是業務無損的。而關掉死鎖檢測意味著可能會出現大量的超時,這是業務有損的。
-
-
-
進行以下流程操作:
-
注意:begin/start transaction 命令並不是一個事務的起點,在執行到它們之後的第一個操作InnoDB表的語句(第一個快照讀語句),事務才真正啟動。如果你想要馬上啟動一個事務,可以使用start transaction with consistent snapshot 這個命令。
-
mysql有兩個檢視的概念
-
view,用查詢語句定義的虛擬表,在呼叫的時候執行查詢語句並生成結果,建立檢視的語法是create view
-
innodb在實現mvcc時用到的一致性讀檢視,用於支援讀提交,可重複度隔離級別的實現。
-
-
快照
-
innodb裡面每個事物有唯一的事物id,叫做transaction id,在事物開始時想innodb事物系統申請的,是按申請順序嚴格遞增的。
-
每行資料有多個版本的,每次更新資料的時候,都會生成一個新的資料版本,並且將transaction id賦值給這個資料版本的事物id 記為row trx_id,同時舊的資料版本要保留,並且在新的資料版本中,能夠有資訊可以直接拿到它。所以資料表中的一行記錄,其實有多個版本,每個版本有自己的row trx_id
-
-
-
虛線就是回滾日誌,V1,V2,V3都不是物理存在的,而是需要根據當前版本和undolog計算出來的。
-
回顧快照
-
可重複度:一個事物啟動後,能夠看到所有已提交事物結果,但是,這個事物執行期間,其它事物的更新對他不可見。因此一個事物只需在啟動的時候說明,,“以我啟動的時刻為準,如果一個數據版本是在我啟動之前生成的,就認;如果是我啟動以後才生成的,我就不認,我必須要找到它的上一個版本”。當然,如果“上一個版本”也不可見,那就得繼續往前找。還有,如果是這個事務自己更新的資料,它自己還是要認的。
-
innodb還為每個事物構造了陣列,用來儲存事物啟動的瞬間,當前啟動了還沒提交的事物id。
-
數組裡事物id的最小值記為低水位,系統裡事物id最大值+1記為高水位
-
試圖陣列和高水位就構成了當前事物的一致性檢視
-
這個檢視陣列把所有的row trx_id分成了幾種不同的情況。
-
-
-
-
-
落在綠色部分,表示這個版本是已提交的事物或者是自己生成的,可見
-
落在紅色部分,這個版本是由將來事物生成的,不可見。
-
如果在黃色部分
-
若row trx_id在陣列中,表示這個版本是由還沒提交的事物生成的,不可見
-
若row trx_id不再陣列中,表示這個版本是已提交的事物生成的,課件
-
-
-
比如上方圖(資料的四個版本),如果有一個事物,它的低水位是18,那麼當他訪問這一行資料時,v4通過u3算出v3,得到值是11。
-
-
第一張圖中的三個事物
-
假設事物A開始前,系統裡只有一個活躍事物id是99,
-
事物A,B,C版本號分別是100,101,102,並且當前只有這四個事物
-
三個事物開始前(1,1)這一行的資料row trx_id是90
-
所以事物A的陣列就是[99,100],B[99,100,101],C[99,100,101,102]
-
-
-
-
第二個有效更新是事務B,把資料從(1,2)改成了(1,3)。這時候,這個資料的最新版本(即row trx_id)是101,而102又成為了歷史版本。
-
在事務A查詢的時候,其實事務B還沒有提交,但是它生成的(1,3)這個版本已經變成當前版本了。但這個版本對事務A必須是不可見的,否則就變成髒讀了。事務A查詢語句的讀資料流程是這樣的
-
找到(1,3)的時候,判斷出row trx_id=101,比高水位大,處於紅色區域,不可見;
-
接著,找到上一個歷史版本,一看row trx_id=102,比高水位大,處於紅色區域,不可見;
-
再往前找,終於找到了(1,1),它的row trx_id=90,比低水位小,處於綠色區域,可見。
-
-
這樣執行下來,雖然期間這一行資料被修改過,但是事務A不論在什麼時候查詢,看到這行資料的結果都是一致的,所以我們稱之為一致性讀。
-
-
一個數據版本,對於一個事務檢視來說,除了自己的更新總是可見以外,有三種情況:
-
版本未提交,不可見
-
版本已提交,但是是在檢視建立後提交的,不可見;
-
版本已提交,而且是在檢視建立前提交的,可見。
-
-
事務A的查詢語句的檢視陣列是在事務A啟動的時候生成的,這時候:
-
(1,3)還沒提交,屬於情況1,不可見;
-
(1,2)雖然提交了,但是是在檢視陣列建立之後提交的,屬於情況2,不可見;
-
(1,1)是在檢視陣列建立之前提交的,可見。
-
可重複讀的核心就是一致性讀(consistent read);而事務更新資料的時候,只能用當前讀。如果當前的記錄的行鎖被其他事務佔用的話,就需要進入鎖等待。
而讀提交的邏輯和可重複讀的邏輯類似,它們最主要的區別是:在可重複讀隔離級別下,只需要在事務開始的時候建立一致性檢視,之後事務里的其他查詢都共用這個一致性檢視;在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的檢視。
小結
-
對於可重複讀,查詢只承認在事務啟動前就已經提交完成的資料;
-
對於讀提交,查詢只承認在語句啟動前就已經提交完成的資料;
-
而當前讀,總是讀取已經提交完成的最新版本。
行鎖
-
innodb支援行鎖,myisam只支援表鎖,同一時刻每張表只能有一條資料被更新
-
在InnoDB事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放, 而是要等到事務結束時才釋放。這個就是兩階段鎖協議。
-
如果你的事務中需要鎖多個行,要把最可能造成鎖衝突、最可能影響並 發度的鎖的申請時機盡量往後放。
-
例子:假設你負責實現一個電影票線上交易業務,顧客A要在影院B購買電影票。我們簡化一點,這個業務需要涉及到以下操作:
-
從顧客A賬戶餘額中扣除電影票價;
-
給影院B的賬戶餘額增加這張電影票價;
-
記錄一條交易日誌。
-
也就是說,要完成這個交易,我們需要update兩條記錄,並insert一條記錄。當然,為了保證交易的原子性,我們要把這三個操作放在一個事務中。那麼,你會怎樣安排這三個語句在事務中的順序呢? 試想如果同時有另外一個顧客C要在影院B買票,那麼這兩個事務衝突的部分就是語句2了。因為它們要更新同一個影院賬戶的餘額,需要修改同一行資料。根據兩階段鎖協議,不論你怎樣安排語句順序,所有的操作需要的行鎖都是在事務提交的時候才 釋放的。所以,如果你把語句2安排在最後,比如按照3、1、2這樣的順序,那麼影院賬戶餘額這一行的鎖時間就最少。這就最大程度地減少了事務之間的鎖等待,提升了併發度
-
-
死鎖:事務A和事務B在互相等待對方的資源釋放,就是進入了死鎖狀態。
-
一種策略是,直接進入等待,直到超時。這個超時時間可以通過引數 innodb_lock_wait_timeout來設定。
-
預設是50s,在正常生產環境中是不可接受的
-
設定時間過短可能會誤傷很多,比如簡單的鎖等待
-
-
另一種策略是,發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將引數innodb_deadlock_detect設定為on,表示開啟這個邏輯。
-
如果有1000條併發更新同一行,那麼會有1000*1000併發量死鎖檢測,導致cpu上升
-
如果確定不會出現死鎖,可以關閉死鎖檢測。但是這種操作本身帶有一定的風險,因為業務設計的時候一般不會把死鎖當做一個嚴重錯誤,畢竟出現死鎖了,就回滾,然後通過業務重試一般就沒問題了,這是業務無損的。而關掉死鎖檢測意味著可能會出現大量的超時,這是業務有損的。
-
-
-
進行以下流程操作:
-
注意:begin/start transaction 命令並不是一個事務的起點,在執行到它們之後的第一個操作InnoDB表的語句(第一個快照讀語句),事務才真正啟動。如果你想要馬上啟動一個事務,可以使用start transaction with consistent snapshot 這個命令。
-
mysql有兩個檢視的概念
-
view,用查詢語句定義的虛擬表,在呼叫的時候執行查詢語句並生成結果,建立檢視的語法是create view
-
innodb在實現mvcc時用到的一致性讀檢視,用於支援讀提交,可重複度隔離級別的實現。
-
-
快照
-
innodb裡面每個事物有唯一的事物id,叫做transaction id,在事物開始時想innodb事物系統申請的,是按申請順序嚴格遞增的。
-
每行資料有多個版本的,每次更新資料的時候,都會生成一個新的資料版本,並且將transaction id賦值給這個資料版本的事物id 記為row trx_id,同時舊的資料版本要保留,並且在新的資料版本中,能夠有資訊可以直接拿到它。所以資料表中的一行記錄,其實有多個版本,每個版本有自己的row trx_id
-
-
-
虛線就是回滾日誌,V1,V2,V3都不是物理存在的,而是需要根據當前版本和undolog計算出來的。
-
回顧快照
-
可重複度:一個事物啟動後,能夠看到所有已提交事物結果,但是,這個事物執行期間,其它事物的更新對他不可見。因此一個事物只需在啟動的時候說明,,“以我啟動的時刻為準,如果一個數據版本是在我啟動之前生成的,就認;如果是我啟動以後才生成的,我就不認,我必須要找到它的上一個版本”。當然,如果“上一個版本”也不可見,那就得繼續往前找。還有,如果是這個事務自己更新的資料,它自己還是要認的。
-
innodb還為每個事物構造了陣列,用來儲存事物啟動的瞬間,當前啟動了還沒提交的事物id。
-
數組裡事物id的最小值記為低水位,系統裡事物id最大值+1記為高水位
-
試圖陣列和高水位就構成了當前事物的一致性檢視
-
這個檢視陣列把所有的row trx_id分成了幾種不同的情況。
-
-
-
-
-
落在綠色部分,表示這個版本是已提交的事物或者是自己生成的,可見
-
落在紅色部分,這個版本是由將來事物生成的,不可見。
-
如果在黃色部分
-
若row trx_id在陣列中,表示這個版本是由還沒提交的事物生成的,不可見
-
若row trx_id不再陣列中,表示這個版本是已提交的事物生成的,課件
-
-
-
比如上方圖(資料的四個版本),如果有一個事物,它的低水位是18,那麼當他訪問這一行資料時,v4通過u3算出v3,得到值是11。
-
-
第一張圖中的三個事物
-
假設事物A開始前,系統裡只有一個活躍事物id是99,
-
事物A,B,C版本號分別是100,101,102,並且當前只有這四個事物
-
三個事物開始前(1,1)這一行的資料row trx_id是90
-
所以事物A的陣列就是[99,100],B[99,100,101],C[99,100,101,102]
-
-
-
-
第二個有效更新是事務B,把資料從(1,2)改成了(1,3)。這時候,這個資料的最新版本(即row trx_id)是101,而102又成為了歷史版本。
-
在事務A查詢的時候,其實事務B還沒有提交,但是它生成的(1,3)這個版本已經變成當前版本了。但這個版本對事務A必須是不可見的,否則就變成髒讀了。事務A查詢語句的讀資料流程是這樣的
-
找到(1,3)的時候,判斷出row trx_id=101,比高水位大,處於紅色區域,不可見;
-
接著,找到上一個歷史版本,一看row trx_id=102,比高水位大,處於紅色區域,不可見;
-
再往前找,終於找到了(1,1),它的row trx_id=90,比低水位小,處於綠色區域,可見。
-
-
這樣執行下來,雖然期間這一行資料被修改過,但是事務A不論在什麼時候查詢,看到這行資料的結果都是一致的,所以我們稱之為一致性讀。
-
-
一個數據版本,對於一個事務檢視來說,除了自己的更新總是可見以外,有三種情況:
-
版本未提交,不可見
-
版本已提交,但是是在檢視建立後提交的,不可見;
-
版本已提交,而且是在檢視建立前提交的,可見。
-
-
事務A的查詢語句的檢視陣列是在事務A啟動的時候生成的,這時候:
-
(1,3)還沒提交,屬於情況1,不可見;
-
(1,2)雖然提交了,但是是在檢視陣列建立之後提交的,屬於情況2,不可見;
-
(1,1)是在檢視陣列建立之前提交的,可見。
-
可重複讀的核心就是一致性讀(consistent read);而事務更新資料的時候,只能用當前讀。如果當前的記錄的行鎖被其他事務佔用的話,就需要進入鎖等待。
而讀提交的邏輯和可重複讀的邏輯類似,它們最主要的區別是:在可重複讀隔離級別下,只需要在事務開始的時候建立一致性檢視,之後事務里的其他查詢都共用這個一致性檢視;在讀提交隔離級別下,每一個語句執行前都會重新算出一個新的檢視。
小結
-
對於可重複讀,查詢只承認在事務啟動前就已經提交完成的資料;
-
對於讀提交,查詢只承認在語句啟動前就已經提交完成的資料;
-
而當前讀,總是讀取已經提交完成的最新版本。