學習來源-圖靈課堂
https://vip.tulingxueyuan.cn
鎖學習參考:
https://juejin.cn/post/7307889500545253395
鎖機制
為了保證資料的一致性,當訪問共享變數的時候我們可以針對共享資料加鎖,但是加鎖要時要注意加鎖的成本,還有加鎖的粒度,還有就是是否會發生死鎖,還有就是發生了死鎖要如何排查,如何解決這個死鎖。
資料的修改,最好是在原來欄位的基礎上進行修改,而不是直接改成某個值,直接修改成一個值可能會導致資料的覆蓋丟失;因為修改是使用當前最新的值進行修改。修改方式
例如要對a的值進行加十操作,最好是使用如下的SQL update table set a = a + 10 where id = 1; 這種操作是在程式碼中對a加過了十,但是可能此時資料庫的值已經被修改了 update table set a = x where id = 1;
MySQL的鎖
- 從效能上分為樂觀鎖(用版本對比或CAS機制)和悲觀鎖,樂觀鎖適合讀操作較多的場景,悲觀鎖適合寫操作較多的場景,如果在寫操作較多的場景使用樂觀鎖會導致比對次數過多,影響效能
- 從對資料操作的粒度分,分為表鎖、頁鎖、行鎖
- 從對資料庫操作的型別分,分為讀鎖和寫鎖(都屬於悲觀鎖),還有意向鎖
讀鎖(共享鎖,S鎖(Shared)):
針對同一份資料,多個讀操作可以同時進行而不會互相影響,比如:
select * from T where id=1 lock in share mode; -- lock in share mode是手動加上讀鎖。
寫鎖(排它鎖,X鎖(eXclusive)):
select * from T where id=1 for update; -- for update手動在查詢的時候加上寫鎖。
針對同一條資料來說其讀寫鎖:讀讀共享;讀寫互斥,寫讀互斥,寫寫互斥。共享時是可以併發操作的;互斥時就是一個執行,另外一個或者多個在阻塞中,可能超時死鎖等。
MySQL InnoDB引擎預設update,delete,insert都會自動給涉及到的資料加上排他鎖,select語句預設不會加任何鎖型別
意向鎖(Intention Lock):
表鎖
手動增加表鎖
lock table 表名稱 read(write),表名稱2 read(write);
例如lock tables t1 read, t2 write; 命令,對t1加表級讀鎖;對t2加表級寫鎖。則其他執行緒寫 t1、讀寫 t2 的語句都會被阻塞。讀t1不阻塞。
同時,在執行 unlock tables 釋放表鎖之前,也只能執行讀 t1、讀寫 t2 的操作。不能在unlock tables之前訪問其他表。表級寫鎖中還是可以進行讀的。
表級別的讀寫鎖是嚴格互斥的。
檢視錶上加過的鎖
show open tables;
刪除表鎖
unlock tables;
頁鎖
行鎖
- InnoDB支援事務(TRANSACTION)
- InnoDB支援行級鎖
注意,加了寫鎖並不是說就不能讀了,只是不能加讀鎖了,還是可以select的,因為預設的select查詢不會加鎖。
查詢鎖(行鎖)
類似於行讀鎖寫鎖。直接鎖住了這條資料。
間隙鎖(Gap Lock)
在RR隔離級別下才生效的。
是針對範圍的加鎖,並不鎖邊界,對這個範圍加鎖之後就可以阻止在這個訪問中插入資料,可以有效的防止幻讀的發生。
SELECT * from account WHERE id = 5 for UPDATE;
這條SQL語句查詢的id是5,因為不存在,就會鎖住5,6,7這三個主鍵值,無法在這三個值中選取主鍵插入表中。
臨鍵鎖(Next-key Locks)
這個是連邊界也鎖住了,類似於大於等於,加上了等於。類似行鎖+間隙鎖。
總結
鎖等待分析
對各個狀態量的說明如下:
- Innodb_row_lock_current_waits: 當前正在等待鎖定的數量
- Innodb_row_lock_time: 從系統啟動到現在鎖定總時間長度
- Innodb_row_lock_time_avg: 每次等待所花平均時間
- Innodb_row_lock_time_max:從系統啟動到現在等待最長的一次所花時間
- Innodb_row_lock_waits: 系統啟動後到現在總共等待的次數
對於這5個狀態變數,比較重要的主要是:
- Innodb_row_lock_time_avg (等待平均時長)
- Innodb_row_lock_waits (等待總次數)
- Innodb_row_lock_time(等待總時長)
尤其是當等待次數很高,而且每次等待時長也不小的時候,我們就需要分析系統中為什麼會有如此多的等待,然後根據分析結果著手製定最佳化計劃。
-- 檢視事務 select * from INFORMATION_SCHEMA.INNODB_TRX; -- 檢視鎖 select * from performance_schema.data_locks; -- 檢視鎖等待 select * from performance_schema.data_lock_waits; -- 釋放鎖,trx_mysql_thread_id可以從INNODB_TRX表裡檢視到 kill trx_mysql_thread_id -- 檢視鎖等待詳細資訊 show engine innodb status\G;
注意:版本是MySQL8.0
- 儘可能讓所有資料檢索都透過索引來完成,避免無索引行鎖升級為表鎖
- 合理設計索引,儘量縮小鎖的範圍
- 儘可能減少檢索條件範圍,避免間隙鎖
- 儘量控制事務大小,減少鎖定資源量和時間長度,涉及事務加鎖的sql儘量放在事務最後執行
- 儘可能用低的事務隔離級別
使用版本號對比的時候,除了要加上where後面的查詢條件,還要在後面加上一個版本號,如果版本號不一致,就不能進行修改;
CAS機制,就是比較並交換,在while true迴圈中,先對比本執行緒快取的值和要修改的目標值是否相等,如果相等就修改;如果不等,就終止本次修改,去迴圈判斷,直到能成功修改。
MVCC機制
- readview和可見性演算法其實就是記錄了sql查詢那個時刻資料庫裡提交和未提交所有事務的狀態。
- 要實現RR隔離級別,事務裡每次執行查詢操作readview都是使用第一次查詢時生成的readview,也就是都是以第一次查詢時當時資料庫裡所有事務提交狀態來比對資料是否可見,當然可以實現每次查詢的可重複讀的效果了。
- 要實現RC隔離級別,事務裡每次執行查詢操作readview都會按照資料庫當前狀態重新生成readview,也就是每次查詢都是跟資料庫裡當前所有事務提交狀態來比對資料是否可見,當然實現的就是每次都能查到已提交的最新資料效果了。
RR級別下,讀是快照讀,一直都是歷史版本的資料,別的事務的任何修改都不能獲取到;但是如果是本事務進行編輯,那麼就是拿最新的資料去編輯,如果沒有別的事務進行編輯或者別的事務編輯已經提交或回滾,此時該事務就進行編輯,此時鎖就變成了行寫鎖,排他鎖,如果一直不提交,就會阻塞別的事務的寫操作。這個要注意。