mysql筆記系列 五 全域性鎖,表鎖,行鎖

2021-10-03 16:11:45 字數 4877 閱讀 5804

資料庫的鎖

分為 全域性鎖,表級鎖,行鎖

1.1 全域性鎖

就是給整個資料庫加鎖,mysql提供了乙個命令為 flush tables with read lock ,使用後 整個庫都將處於唯讀狀態

其他執行緒的 資料更新操作(增刪改), 資料定義語句(建表,修改表結構) 和更新類事務的提交語句 都阻塞。

全域性鎖的場景:做全庫邏輯備份,也就是將資料庫的資料匯出進行備份,這個期間不能讓其他新的資料和操作進來。

比如說,從表a扣10塊錢,再在表b加10塊,如果不加鎖就備份,假設先備份b表,再備份a表,最終備份的a表是已經扣了錢的,但是b表備份的早

備份是沒扣錢的。

也就是 如果不全域性鎖就備份,那麼備份就不是同乙個時間點的資料快照,這個資料是有問題的。

mysql提供了可重複讀的事務隔離級別,這個級別下事務開始時產生了資料在同乙個時間點邏輯檢視,因此 在mysql提供的備份工具

mysqldump中,開啟single-transaction 可以拿到一致性檢視,這個時候備份匯出時候,其他執行緒是可以寫的。

但是為什麼還存在 flush tables with read lock 呢?因為不是所有的儲存引擎都支援這個事務隔離級別,比如說myisam引擎,這種不支援事務的引擎,

就需要用到這個全域性鎖了。

而且要注意:要這個庫裡面所有的表都用的支援事務的儲存引擎,才能開啟single-transaction來備份庫,不然就要加全域性鎖。

另外,使用set global readonly=true ,也可以將庫改為讀庫,禁止掉寫,但是如果是備份 最好不要用這個,因為如果用的是全域性鎖,當客戶端異常掉線之後,鎖是會自動釋放的。但是使用全域性變數readonly,如果不改回來,資料庫會一直處於可讀狀態。

而且readonly還可能被用來做其他業務邏輯,比如說在某個程式裡面被用來判斷是主庫還是從庫。

開啟全域性鎖的風險:

期間資料庫為唯讀,如果對主庫操作,業務就停了,如果對從庫操作,那麼期間從庫就不能同步資料進來,會造成主從延遲。

1.2 表級鎖 分為表鎖和元資料鎖

1.2.1 表鎖lock tables … read/write

read是讀鎖,加鎖之後,不能寫,只能讀。

write 是寫鎖,加了寫鎖之後,其他執行緒不能讀 也不能寫,只有本執行緒能讀寫,寫鎖也就是獨佔鎖。

也即是 讀鎖 ,讀讀不互斥,讀寫互斥。 寫鎖 ,讀寫互斥,寫寫互斥。

表鎖可以主動執行unlock釋放,客戶端連線斷開之後也會自動釋放。

在沒有更細粒度的鎖的時候,表鎖是常用來處理併發的形式,但是innodb支援行鎖,表鎖影響面太大。

1.2.1 元資料鎖 mdl (metadatalock)

元資料鎖不需要顯式使用,在對錶進行讀寫時,會自動加鎖, 鎖的是表結構。

以防止類似於你在讀寫資料的時候,別人把字段刪了這種情況,保證讀寫的正確性,也就是保證dml和ddl的併發正確性。

在對錶進行增刪改查操作的時候,加mdl讀鎖 ,任何人不能改這個表的結構,但是可以檢視表結構。

在對錶進行表結構變更的時候,加mdl寫鎖,任何人不能讀寫這個表的結構,只有加鎖的執行緒完成結構變更之後釋放鎖才行。

也就是如果同時有兩個人進行表結構變更,只有一方會執行,另一方等待。

也就是如果有人在進行增刪改查的操作,會加dml讀鎖,

另乙個人去修改表結構,需要加dml寫鎖,此時會被阻塞,但是需要注意的是 之後的增刪改查

都需要去獲取dml讀鎖,此時也會被阻塞,雖然之前申請dml寫鎖沒有成功 ,但是為了防止飢餓,會把後面申請dml讀鎖的執行緒都阻塞

直到這個dml寫鎖成功申請 且釋放之後。

這裡面存在的乙個風險,一開始的那個人進行增刪改查操作,如果執行時間特長的事務,dml鎖是事務提交的時候才會釋放,那麼後面的操作就都阻塞了。

也就是 當執行大事務的時候,有人去修改表字段,會導致後面要執行增刪改查的執行緒都阻塞掉,大面積阻塞,是很大風險的。

因此,表結構變更需要謹慎,如果在業務繁忙期間,去做表結構變更,再加dml的寫鎖時 如果堵塞, 會導致業務讀寫表時候也阻塞,因為要獲取dml的讀鎖。

同時對乙個表進行加欄位,刪字段,修改字段,加索引 ,會掃瞄全量的資料,對大表操作的時候要謹慎。

1.2.2 online ddl

mysql 5.6的優化,也就是在拿到mdl寫鎖之後,會降級成mdl讀鎖,這樣就不會阻塞之後的增刪改查了

然後等增刪改查操作完之後,再公升級成mdl寫鎖,然後釋放

步驟如下:

1.拿mdl寫鎖

2.降級成mdl讀鎖

3.真正做ddl

4.公升級成mdl寫鎖

5.釋放mdl

如果1 2 4 5 沒有鎖衝突,執行時間是非常短的,第三部ddl佔了絕大部分時間,這個期間這個表可以正常讀寫資料,因此稱為online

ddl 可分為copy和inplace兩種

1.copy 這部分是offline的 也就是ddl期間dml不能操作, myql 5.6之前ddl的操作方式均是如此。

流程是先建立臨時表,然後將資料寫入到臨時表,再刪除原表,然後重新命名臨時表為原表名。

innodb中,但凡不支援inplace的操作都會使用這種copy形式,myisam是只能使用copy形式,使用copy模式的有

刪除主鍵,更改列資料型別,轉換字符集,表空間加密,部分分割槽操作,這些都涉及到了修改.ibd 資料。

2.inplace 所有的操作都在innodb中完成,不需要經過臨時表的中轉。 innodb中除了(刪除主鍵,更改列資料型別,轉換字符集,表空間加密,部分分割槽操作) 之外的,都是inplace模式。

inplace模式中。除了(第一次新增fulltext索引,新增spattal索引) 之外的操作,都是inline的,不和dml衝突。

inplace又可以根據是否重建表分為 rebuild 和no-rebuild兩種

rebuild 重建表:會在原表路徑下建立.frm和.ibd檔案(.frim儲存了每個表的元資料,包括表結構的定義等,.ibd存放表的資料和索引的檔案),消耗的io會比較多,期間 原表可以修改,修改的記錄會被寫入到row_log中,這部分操作會在ddl提交之後應用到新的表空間中。

no-rebuild 非重建表:只在原表路徑下建立.frm檔案,只修改原資料項,不生成row_log 速度比較快。

online ddl推出以前,執行ddl主要有兩種方式copy方式和inplace方式,inplace方式又稱為(fast index creation)。相對於copy方式,inplace方式不拷貝資料,因此較快。但是這種方式僅支援新增、刪除索引兩種方式,而且與copy方式一樣需要全程鎖表,實用性不是很強。

copy流程:

1.建立帶索引的臨時表

2.給原表加上dml讀鎖

3.將原表資料copy到臨時表

4.重新命名臨時表,加上dml寫鎖,禁止掉讀寫

5.完成索引建立

inplace

1.新建索引的資料字典.frm檔案

2.給原表加上dml讀鎖

3.讀取聚簇索引 構建新的索引,排序並插入到新索引

4.等待當前表的唯讀事務提交

5.完成索引建立

可以看到 inplace方式 只是修改了.frm檔案

總結:online ddl是mysql5.6之後的優化, 對inpalce的操作 新增了可以並行dml的行為,核心是鎖降級和row_log記錄ddl期間的dml操作。

注意onlineddl的主從備份, 如果主庫執行了ddl, 即使是onlineddl , 通過binlog傳到從庫的時候,從庫是先執行ddl操作,再執行dml操作,並非是同時,所以對大表的ddl操作會導致從庫同步延遲。

1.3 行鎖

針對資料表中行記錄的鎖,在需要的時候加上,在事務結束的時候釋放。

因此:如果事務中鎖多個行,要把最有可能造成鎖衝突,最有可能影響併發度的鎖盡量往後放,因為鎖都是在事務結束的時候釋放,越放到後面,持有的時間相對短一些。

死鎖:當併發系統中不同執行緒出現了迴圈資源依賴,設計的執行緒都在等待別的執行緒釋放資源時,就會導致這幾個執行緒都進入無限等待的狀態,稱為死鎖。

在innodb中,有兩種方式處理死鎖:

1.超時釋放,檢測鎖等待的時間,如果超時就會釋放,innodb_lock_wait_timeout ,但是這個時間,預設是50秒,如果設定的短了,萬一是普通鎖等待,也會被釋放。

2.主動鎖檢測,innodb_deadlock_detect的預設值是on , 優點是可以快速發現並進行處理,但是缺點是 會耗費大量的cpu資源。 檢測邏輯是每乙個被阻塞的執行緒,都會找到阻塞自己的執行緒,然後看這個執行緒又是被誰阻塞的,依次類推,如果發現是乙個環,說明死鎖了。 時間複雜度是n , 如果有1000個執行緒併發更新同一行,死鎖檢測就要對每個執行緒都進行這個操作,數量就是100萬, 因此就會看到cpu使用率很好,但是執行不了幾個事務。

解決方式:

第一是關閉死鎖檢測,但是這個是很危險的,因為如果沒有死鎖檢測,真的發生了死鎖,就會引發大面積超時,對業務來說是致命的。

第二,業務設計上進行優化,比如加入佇列進行削峰填谷,將併發度降下來,或者使用分段鎖的思想,將要更新的記錄存到多個記錄上,最終查詢的時候使用這些記錄的和,缺點就是提高了業務複雜度。

總之,核心是要控制併發度。

注意:innodb的行鎖是鎖的索引記錄,如果沒有建索引,會導致鎖表的,只update一條記錄也會鎖表

update t set t.name=『abc』 where t.name=『cde』; 如果name列無索引,那麼則會鎖表。

補充:假設刪除乙個表裡面的10000行資料,有三種方式

第一種,直接執行 delete from t limit 10000;

第二種,在乙個連線中迴圈執行 20 次 delete from t limit 500;

第三種,在 20 個連線中同時執行 delete from t limit 500。

第一種由於資料量太多,會導致長事務

第二種,將大事務分成了很多個小事務,比較合理

第三種,等於是人為製造併發更新衝突。

應該要選第二種。

《MySQL必懂系列》全域性鎖 表級鎖 行鎖

mysql提供了不同等級的鎖,按限制能力的劃分,分為全域性鎖 表鎖 行鎖。本文會描述不同鎖的應用場景與實現原理。全域性鎖就是對整個mysql資料庫加鎖,mysql中的命令是 flush tables with read lock ftwrl 在執行這個命令之後,mysql進入全域性鎖的狀態,整個資料...

Mysql 學習(五)MySQL的全域性鎖和表鎖

1.全域性鎖就是對整個資料庫例項加鎖 2.mysql 提供了乙個加全域性讀鎖的方法,命令是flush tables with read lock ftwrl 3.當你需要讓整個庫處於唯讀狀態的時候,可以使用這個命令,之後其他執行緒的以下語句會被阻塞 資料更新語句 資料的增刪改 資料定義語句 包括建表...

MySQL全域性鎖和表鎖

對整個資料庫加鎖,語句為 flush tables with read lock ftwrl 加鎖之後不可以進行增刪改,也不能做ddl,是乙個整庫唯讀的狀態,一般只有在做全庫邏輯備份時才需要全域性鎖。可以看到,如果採用這種方式對庫加鎖的話,雖然保證了一致性,但十分影響業務。所以,應該盡量少採用這種方...