內(nèi)容
- 鎖種類(lèi)
- 鎖類(lèi)型
- 一致性非鎖定讀
- 一致性鎖定讀
- 鎖算法
一 鎖種類(lèi)
根據(jù)加鎖的范圍來(lái)說(shuō)竹椒,鎖可以分為:全局鎖滑废、表級(jí)鎖贞滨、行級(jí)鎖
1 全局鎖(FTWRL)
- 加鎖方式:flush tables with read lock
- 用途:全局主要用來(lái)全庫(kù)邏輯備份竟痰,加了全局鎖之后盘榨,會(huì)阻塞整庫(kù)其他線程的更新操作郊愧,這個(gè)時(shí)候可以通過(guò)select把整庫(kù)導(dǎo)出到文本做備份朴译;
- 缺點(diǎn):如果在主庫(kù)執(zhí)行全局鎖,主庫(kù)就停寫(xiě)属铁,如果在備庫(kù)眠寿,那么會(huì)導(dǎo)致主庫(kù)延遲;
- 還可以用其他方式代替全局鎖嗎焦蘑?
- 如果所有表都是innodb表盯拱,并且隔離級(jí)別是可重復(fù)讀的話,可以通過(guò)事務(wù)來(lái)實(shí)現(xiàn),在一個(gè)事務(wù)內(nèi)進(jìn)行讀取數(shù)據(jù)狡逢,事務(wù)開(kāi)啟時(shí)會(huì)生成一個(gè)一致性視圖宁舰,之后在當(dāng)前事務(wù)讀取的數(shù)據(jù)都不會(huì)受其他線程更新影響
2 表級(jí)鎖
- 種類(lèi):表級(jí)鎖有兩種:表鎖和MDL(metadata lock)
- 表鎖加鎖:lock tables … read/write
- MDL:mdl鎖是隱示的加鎖,當(dāng)訪問(wèn)一個(gè)表時(shí)會(huì)被主動(dòng)加上
1.當(dāng)對(duì)表進(jìn)行增刪改查時(shí)奢浑,會(huì)申請(qǐng)獲取mdl讀鎖蛮艰;當(dāng)進(jìn)行表結(jié)構(gòu)變更時(shí),會(huì)申請(qǐng)mdl寫(xiě)鎖雀彼;
2.mdl讀鎖之間不阻塞壤蚜,可以用多個(gè)線程同時(shí)獲取同一個(gè)表的mdl讀鎖;mdl讀鎖會(huì)堵塞mdl寫(xiě)鎖徊哑,mdl寫(xiě)鎖會(huì)堵塞mdl 讀寫(xiě)鎖袜刷,當(dāng)一個(gè)表的mdl讀鎖被占用了后,不能再對(duì)該表進(jìn)行表結(jié)構(gòu)變更实柠,ddl會(huì)被堵塞水泉,假設(shè)此時(shí)又有其他的線程來(lái)申請(qǐng)mdl讀鎖,那么這些讀請(qǐng)求都會(huì)被堵塞窒盐,所以如果對(duì)熱點(diǎn)表進(jìn)行ddl操作草则,有可能就會(huì)堵塞大量請(qǐng)求,然后又會(huì)不停重試蟹漓,導(dǎo)致打爆整個(gè)庫(kù)
3 行級(jí)別
- 兩階段鎖協(xié)議:在innodb事務(wù)中炕横,行鎖是需要的時(shí)候才加上的,但是要等到事務(wù)結(jié)束了才會(huì)釋放鎖葡粒;這個(gè)機(jī)制給我們啟示是:如果你的事務(wù)中需要鎖多個(gè)行份殿,要把最可能造成鎖沖突、最可能影響并發(fā)度的鎖盡量放在事務(wù)的后面去執(zhí)行嗽交;
- 死鎖和死鎖檢測(cè)
1.當(dāng)并發(fā)系統(tǒng)中不同線程出現(xiàn)循環(huán)資源依賴(lài)卿嘲,涉及的線程都在等待別的線程釋放資源時(shí),就會(huì)導(dǎo)致這幾個(gè)線程都進(jìn)入無(wú)限等待的狀態(tài)夫壁,稱(chēng)為死鎖 - 當(dāng)發(fā)生死鎖是有何種策略解決拾枣?
- 直接等待,超時(shí)釋放盒让,超時(shí)時(shí)間是有參數(shù):innodb_lock_wait_timeout設(shè)定的梅肤;
2.發(fā)起死鎖檢測(cè),主動(dòng)回滾死鎖中一個(gè)事務(wù)邑茄,讓其他的事務(wù)得也執(zhí)行姨蝴,將參數(shù):innodb_deadlock_detect 設(shè)置為 on(默認(rèn)就是on),表示開(kāi)啟這個(gè)邏輯肺缕。
- 直接等待,超時(shí)釋放盒让,超時(shí)時(shí)間是有參數(shù):innodb_lock_wait_timeout設(shè)定的梅肤;
- 死鎖檢測(cè)
1.死鎖檢測(cè)比較耗cpu資源左医,它是一個(gè)o(n)的操作授帕,,所以當(dāng)并發(fā)比較大的時(shí)候炒辉,發(fā)現(xiàn)單位時(shí)間內(nèi)執(zhí)行的事務(wù)缺不多豪墅,但是cpu確被打滿,那很有可能是死鎖檢測(cè)耗掉了大量的cpu資源 - 控制死鎖
1.減少死鎖的方向是控制相同資源的并發(fā)事務(wù)量黔寇,一種業(yè)務(wù)做法是把資源分散偶器,分散對(duì)相同資源的并發(fā)事務(wù)沖突;
一 鎖類(lèi)型
概括性來(lái)說(shuō)缝裤,mysql innodb存儲(chǔ)引擎有兩種鎖類(lèi)型屏轰,分別是行級(jí)別的行鎖和表級(jí)別的意向鎖;
行鎖有如下兩種類(lèi)型:
- 行共享鎖(S Lock):允許事務(wù)讀取一行數(shù)據(jù)憋飞;
-
行排他鎖(X Lock): 允許事務(wù)刪除或是更新一條數(shù)據(jù)霎苗;
如果一個(gè)事務(wù)想要更新或是刪除一行數(shù)據(jù),就必須獲取這行數(shù)據(jù)的排他鎖榛做;如果已經(jīng)有其他事務(wù)獲取該行的共享鎖唁盏,就會(huì)阻塞其他事務(wù)獲取該行的排他鎖,但是不阻塞獲取共享检眯,行共享和排他鎖的兼容性如下:
image.png
為了支持多種粒度的鎖機(jī)制厘擂,innodb也支持表級(jí)別的加鎖,要想獲取細(xì)粒度的鎖(行鎖)锰瘸,就必須先獲取更粗粒度的鎖(意向鎖)刽严,意向鎖有兩種:
- 意向共享鎖(IS Lock): 事務(wù)想要獲取表中的某行或是某幾行的共享鎖;
-
意向排他鎖(IX Lock): 事務(wù)想要獲取表中的某行或是某幾行的排他鎖避凝;
意向鎖和行鎖的兼容性如下圖所示:
image.png
二 一致性非鎖定讀
一個(gè)事務(wù)想要讀取一行記錄舞萄,需要先獲取這行數(shù)據(jù)的行共享鎖,如果此時(shí)另外一個(gè)事務(wù)先獲取該行的行排他鎖管削,根據(jù)行鎖的兼容性倒脓,后來(lái)的事務(wù)變不能獲取行共享,只能等待行排他鎖釋放含思,這無(wú)疑是不得行了把还,Innodb通過(guò)MVCC機(jī)制實(shí)現(xiàn)了寫(xiě)不阻塞讀,讀取的數(shù)據(jù)如果有其他事務(wù)正在進(jìn)行更新或是刪除操作茸俭,則讀取該行數(shù)據(jù)的上一個(gè)事務(wù)版本號(hào)的數(shù)據(jù),讀取的是一個(gè)快照數(shù)據(jù)安皱,這個(gè)數(shù)據(jù)是通過(guò)undo日志來(lái)獲取的调鬓,undo日志也即是innodb mvcc機(jī)制實(shí)現(xiàn)的根本;
三 一致性鎖定讀
在默認(rèn)的可重復(fù)讀隔離級(jí)別酌伊,innodb讀取數(shù)據(jù)是一致性非鎖定讀腾窝,讀不加鎖缀踪,寫(xiě)不阻塞讀;但是有些業(yè)務(wù)場(chǎng)景需要我們讀取數(shù)據(jù)時(shí)手動(dòng)上鎖以保證數(shù)據(jù)的一致性虹脯,有兩種方式手動(dòng)上鎖實(shí)現(xiàn)一致性鎖定讀
- select ... for update: 給滿足條件的行加上排他鎖驴娃;
- select ... lock in share mode: 給滿足條件的行加上共享鎖;
注意:手動(dòng)加鎖需要在一個(gè)事務(wù)中才生效循集,也即:需要手動(dòng)開(kāi)啟一個(gè)事務(wù)唇敞,begin /../ commit
四 鎖算法
innodb有三種鎖算法,分別是:
- Record Lock: 所有當(dāng)前記錄
- Gap Lock: 間隙鎖咒彤,鎖定一個(gè)范圍疆柔,但是不包括記錄本身,例如:(-∞镶柱,1)(1旷档, 5)(5,+ ∞)
- Next-Key Lock: Record Lock+Gap Lock歇拆, 同樣鎖定一個(gè)范圍鞋屈,但是包括記錄本身,例如:(-∞故觅,1](1厂庇, 5](5,+ ∞)
規(guī)則:
- innodb的Record Lock是根據(jù)索引項(xiàng)來(lái)加鎖的逻卖,如果查詢(xún)條件不是根據(jù)索引項(xiàng)來(lái)查詢(xún)宋列,會(huì)鎖定整張表記錄
說(shuō)明:例如:如果在一個(gè)事務(wù)中執(zhí)行:select * from t1 where id=4 for update; 其中id并沒(méi)有設(shè)置為主鍵,也沒(méi)有添加任何索引评也,則此時(shí)會(huì)阻塞其他事務(wù)的任何插入 更新 刪除操作炼杖; - InnoDB通過(guò)索引來(lái)實(shí)現(xiàn)行鎖,而不是通過(guò)鎖住記錄盗迟。因此坤邪,當(dāng)操作的兩條不同記錄擁有相同的索引時(shí),也會(huì)因?yàn)樾墟i被鎖而發(fā)生等待罚缕。
- 由于InnoDB的索引機(jī)制艇纺,數(shù)據(jù)庫(kù)操作使用了主鍵索引,InnoDB會(huì)鎖住主鍵索引邮弹;使用非主鍵索引時(shí)黔衡,InnoDB會(huì)先鎖住非主鍵索引,再鎖定主鍵索引腌乡。
- 當(dāng)查詢(xún)的索引是唯一索引時(shí)盟劫,InnoDB存儲(chǔ)引擎會(huì)將Next-Key Lock降級(jí)為Record Lock,即只鎖住索引本身与纽,而不是范圍侣签。
- InnoDB對(duì)于輔助索引有特殊的處理塘装,不僅會(huì)鎖住輔助索引值所在的范圍,還會(huì)將其下一鍵值加上Gap LOCK影所。
說(shuō)明:例如:如果某個(gè)列有1蹦肴,3,5三個(gè)索引值猴娩,如果在一個(gè)事務(wù)中執(zhí)行:select * from t1 where id=3 for update時(shí)會(huì)同時(shí)鎖住(1,3]和(3,5)兩個(gè)區(qū)間阴幌,其他事務(wù)如果插入這個(gè)兩個(gè)區(qū)間的值是不得行的,會(huì)阻塞胀溺,但是插入非這連個(gè)區(qū)間是可以的裂七,比如插入:insert into t1 values(7) - InnoDB使用Next-Key Lock機(jī)制來(lái)避免Phantom Problem(幻讀問(wèn)題)。
說(shuō)明:個(gè)人理解在可重復(fù)讀隔離級(jí)別下仓坞,Innodb應(yīng)該是通過(guò)Next-Key Lock機(jī)制+MVCC機(jī)制共同保證不會(huì)發(fā)生幻讀問(wèn)題, MVCC不能完全解決幻讀問(wèn)題背零,MVCC可以解決快照讀問(wèn)題,但是當(dāng)前讀問(wèn)題不能解決无埃,舉例說(shuō)明如下:
1 MVCC能解決快照度的幻讀問(wèn)題
例子1
創(chuàng)建表t2
CREATE TABLE `t2` (
`id` int(11) DEFAULT NULL,
`name` varchar(20) DEFAULT NULL,
KEY `id` (`id`)
) ENGINE=InnoDB
開(kāi)啟兩個(gè)事務(wù)tx1和tx2徙瓶,先在tx1事務(wù)中查詢(xún)
mysql> begin;
mysql> select * from t2 where id>5;
+------+------+
| id | name |
+------+------+
| 6 | dd |
| 8 | dd |
| 7 | dd |
| 9 | rr |
| 10 | re |
+------+------+
5 rows in set (0.00 sec)
然后事務(wù)2中,插入一行記錄嫉称,并提交
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t2 values(12,"rr");
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
結(jié)論:
再在事務(wù)一查詢(xún)相同語(yǔ)句侦镇,查詢(xún)不到12這條記錄,因?yàn)槠胀ǖ膕elect是快照讀织阅,快照讀根據(jù)MVCC機(jī)制不會(huì)讀到大于當(dāng)前事務(wù)版本的數(shù)據(jù)壳繁,所以MVCC可以解決快照讀的幻讀問(wèn)題
2 MVCC不能解決當(dāng)前讀的幻讀問(wèn)題,當(dāng)前讀是讀取最新的數(shù)據(jù)荔棉,更新刪除等就是當(dāng)前讀
例子2:
- 事務(wù)1執(zhí)行以下語(yǔ)句闹炉,for update
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t2 where id>5 for update;
+------+------+
| id | name |
+------+------+
| 6 | dd |
| 8 | dd |
| 7 | dd |
| 9 | rr |
| 10 | re |
| 12 | rr |
| 13 | rr |
+------+------+
7 rows in set (0.00 sec)
- 然后再事務(wù)2中執(zhí)行,插入14語(yǔ)句
mysql> begin;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into t2 values(14,"rr");
結(jié)論:
發(fā)現(xiàn)事務(wù)2進(jìn)行鎖等待润樱,不能插入渣触,那是因?yàn)槭聞?wù)1中for update對(duì)加了next-key lock,對(duì)區(qū)間(5,+∞)進(jìn)行了加鎖壹若,導(dǎo)致這個(gè)區(qū)間的插入操作都等待鎖嗅钻,所以next-key lock能解決到一些幻讀問(wèn)題
例子3
先在事務(wù)1執(zhí)行下面操作
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t2;
+------+------+
| id | name |
+------+------+
| 1 | dd3 |
| 2 | dd3 |
| 3 | dd |
| 4 | dd |
| 6 | dd |
| 8 | dd |
| 7 | dd |
| 9 | rr |
| 10 | re |
| 12 | rr |
| 13 | rr |
+------+------+
11 rows in set (0.00 sec)
此時(shí)事務(wù)一種查詢(xún)到11條數(shù)據(jù),事務(wù)一不提交店展,然后開(kāi)啟事務(wù)2养篓,在事務(wù)2中執(zhí)行一個(gè)插入操作,并commit
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t2 values(14, "22");
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
然后我們?cè)僭谑聞?wù)1中執(zhí)行更新操作,然后相同的查詢(xún)
mysql> update t2 set name='aa';
Query OK, 12 rows affected (11.66 sec)
Rows matched: 12 Changed: 12 Warnings: 0
mysql> select * from t2;
+------+------+
| id | name |
+------+------+
| 1 | aa |
| 2 | aa |
| 3 | aa |
| 4 | aa |
| 6 | aa |
| 8 | aa |
| 7 | aa |
| 9 | aa |
| 10 | aa |
| 12 | aa |
| 13 | aa |
| 14 | aa |
+------+------+
12 rows in set (0.00 sec)
結(jié)論:
我們發(fā)現(xiàn)在事務(wù)一中讀取到了事務(wù)二插入的數(shù)據(jù)赂蕴,在同一個(gè)事務(wù)中兩次讀取的數(shù)據(jù)不一致觉至,發(fā)生了幻讀,原因在于發(fā)生了一次當(dāng)前讀(更新操作)睡腿,所以此時(shí)MVCC沒(méi)有解決到幻讀問(wèn)題
通過(guò)例子1 2 3我們得出最終結(jié)論:
- 在可重復(fù)讀隔離級(jí)別下语御,MVCC機(jī)制可以解決快照讀的幻讀問(wèn)題,但是如果有當(dāng)前讀席怪,那就有可能發(fā)生幻讀了
- 如果有當(dāng)前讀应闯,想要預(yù)防幻讀,可以通過(guò)加行鎖挂捻,通過(guò)Next-Key Lock算法機(jī)制保證在記錄周?chē)秶苌湘i防止幻讀問(wèn)題
引用:
《MYSQL技術(shù)內(nèi)幕 Innodb存儲(chǔ)引擎》
《丁奇45講》