目錄:
1.鎖的定義與分類(lèi)(表径簿、行甜奄、頁(yè))
2.鎖相關(guān)的語(yǔ)句(查看鎖)
3.mysql事務(wù)
4.樂(lè)觀鎖和悲觀鎖
5.數(shù)據(jù)庫(kù)死鎖
1.鎖的定義與分類(lèi)
1.1.鎖的定義
鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程并發(fā)訪問(wèn)某一資源的機(jī)制。
在數(shù)據(jù)庫(kù)中探越,除傳統(tǒng)的計(jì)算資源(如CPU朵逝、RAM稚虎、I/O等)的爭(zhēng)用以外,數(shù)據(jù)也是一種供許多用戶共享的資源裁眯。如何保證數(shù)據(jù)并發(fā)訪問(wèn)的一致性鹉梨、有效性是所有數(shù)據(jù)庫(kù)必須解決的一個(gè)問(wèn)題,鎖沖突也是影響數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)性能的一個(gè)重要因素穿稳。從這個(gè)角度來(lái)說(shuō)存皂,鎖對(duì)數(shù)據(jù)庫(kù)而言顯得尤其重要,也更加復(fù)雜逢艘。
1.2.鎖的分類(lèi)
開(kāi)鎖旦袋、加鎖速度、死鎖它改、粒度疤孕、并發(fā)性能只能就具體應(yīng)用的特點(diǎn)來(lái)說(shuō)哪種鎖更合適
1)、從對(duì)數(shù)據(jù)操作的類(lèi)型(讀\寫(xiě))分
讀鎖(共享鎖): 針對(duì)同一份數(shù)據(jù)央拖,多個(gè)讀操作可以同時(shí)進(jìn)行而不會(huì)互相影響
寫(xiě)鎖(排它鎖): 當(dāng)前寫(xiě)操作沒(méi)有完成前祭阀,它會(huì)阻斷其他寫(xiě)鎖和讀鎖
2)、從對(duì)數(shù)據(jù)操作的粒度分
表鎖(偏讀)
行鎖(偏寫(xiě))
頁(yè)鎖(了解)
1.2.1.表鎖(偏讀)
mysql的表級(jí)鎖有兩種模式:
表共享讀鎖(Table Read Lock)
表獨(dú)占寫(xiě)鎖(Table Write Lock)
特點(diǎn):
偏向MyISAM存儲(chǔ)引擎鲜戒,開(kāi)銷(xiāo)小专控,加鎖快;無(wú)死鎖遏餐;鎖定粒度大伦腐,發(fā)生鎖沖突的概率最高,并發(fā)度最低失都。
MyISAM在執(zhí)行查詢(xún)語(yǔ)句前柏蘑,會(huì)自動(dòng)給涉及的所有表加讀鎖颖系,在執(zhí)行增刪改操作前,會(huì)自動(dòng)給涉及的表加寫(xiě)鎖
讀鎖會(huì)被阻塞寫(xiě)辩越,但不會(huì)阻塞寫(xiě)嘁扼。而寫(xiě)鎖則會(huì)把讀和寫(xiě)都阻塞。
案例分析:
①表鎖–讀鎖
create table mylock (
id int not null primary key auto_increment,
name varchar(20)
) engine myisam; // 這里用MyISAM存儲(chǔ)引擎
類(lèi)似再建一個(gè)book表和mylock一樣就行黔攒;
【手動(dòng)增加表鎖】lock table 表名字 read(write), 表名字2 read(write), 其他;
【釋放鎖】unlock tables;
【查看表上加過(guò)的鎖】show open tables;
mysql> lock table mylock read, book write;
mysql> show open tables;
+----------+-------+--------+-------------+
| DATABASE | TABLE | In_use | Name_locked |
+----------+-------+--------+-------------+
| dbtest | book | 1 | 0 |
+----------+-------+--------+-------------+
| dbtest | mylock| 1 | 0 |
+----------+-------+--------+-------------+
·Database:含有該表的數(shù)據(jù)庫(kù)趁啸。
·Table:表名稱(chēng)。
·In_use:表當(dāng)前被查詢(xún)使用的次數(shù)督惰。如果該數(shù)為零不傅,則表是打開(kāi)的,但是當(dāng)前沒(méi)有被使用赏胚。
·Name_locked:表名稱(chēng)是否被鎖定访娶。名稱(chēng)鎖定用于取消表或?qū)Ρ磉M(jìn)行重命名等操作。
mysql> update mylock set name='a2' where id=1;
ERROR 1099(HY000): Table 'mylock' was locked with a READ lock and can't be updated
如果是另外一個(gè)session2執(zhí)行上面的update語(yǔ)句觉阅,不會(huì)報(bào)錯(cuò)崖疤,sql被阻塞從而一直在等待,直到被鎖的session執(zhí)行unlock tables 才會(huì)執(zhí)行典勇。
②表鎖–寫(xiě)鎖
session_1 | session_2 |
---|---|
獲取mylock的write鎖定mysql>lock tables mylock write; | 待Sesson_1開(kāi)啟鎖后, session2再連接終端 |
當(dāng)前session1對(duì)鎖定表的查/更新/修改等操作都可以執(zhí)行 | 其他session對(duì)鎖定表的查詢(xún)被阻塞劫哼,需要等待鎖被釋放;如果沒(méi)有被阻塞割笙,可能是mysql的緩存权烧,換個(gè)查詢(xún)條件即可 |
釋放鎖mysql> unlock tables; | session2獲得鎖,查詢(xún)返回(可以看到返回時(shí)間有點(diǎn)長(zhǎng)) |
1.2.2.行鎖(偏寫(xiě))
特點(diǎn):
偏向InnoDB存儲(chǔ)引擎伤溉,開(kāi)銷(xiāo)大般码,加鎖慢;會(huì)出現(xiàn)死鎖乱顾;鎖定粒度最小板祝,發(fā)生鎖沖突的概率最低,并發(fā)度也最高糯耍;
InnoDB與MyISAM的最大不同有兩點(diǎn):一是支持事務(wù)扔字;二是采用了行級(jí)鎖;
雖然InnoDB引擎的表可以用行級(jí)鎖温技,但這個(gè)行級(jí)鎖的機(jī)制依賴(lài)于表的索引革为,如果表沒(méi)有索引,或者sql語(yǔ)句沒(méi)有使用索引舵鳞,那么仍然使用表級(jí)鎖震檩;
2.鎖相關(guān)語(yǔ)句
【手動(dòng)增加表鎖】lock table 表名字 read(write), 表名字2 read(write), 其他;
【釋放鎖】unlock tables;
-
【查看哪些表被加鎖了】mysql> show open tables;
mysql> show open tables; +----------+-------+--------+-------------+ | DATABASE | TABLE | In_use | Name_locked | +----------+-------+--------+-------------+ | dbtest | book | 1 | 0 | +----------+-------+--------+-------------+ | dbtest | mylock| 1 | 0 | +----------+-------+--------+-------------+ ·Database:含有該表的數(shù)據(jù)庫(kù)。 ·Table:表名稱(chēng)。 ·In_use:表當(dāng)前被查詢(xún)使用的次數(shù)抛虏。如果該數(shù)為零博其,則表是打開(kāi)的,但是當(dāng)前沒(méi)有被使用迂猴。 ·Name_locked:表名稱(chēng)是否被鎖定慕淡。名稱(chēng)鎖定用于取消表或?qū)Ρ磉M(jìn)行重命名等操作。
-
【如何分析表鎖定】mysql> show status like 'tables%';
mysql> show status like 'tables%'; +------------------------+-------+ | Variable_name | TABLE | +------------------------+-------+ | Table_locks_immediate | 105 | +------------------------+-------+ | Table_locks_waited | 1 | +------------------------+-------+ 這里有兩個(gè)狀態(tài)變量記錄MySQL內(nèi)部表級(jí)鎖定的情況沸毁,兩個(gè)變量說(shuō)明如下: a).Table_locks_immediate: 產(chǎn)生表級(jí)鎖定的次數(shù)峰髓,表示可以立即獲取鎖的查詢(xún)次數(shù),每立即獲取鎖值加1息尺; b).Table_locks_waited: 出現(xiàn)表級(jí)鎖定爭(zhēng)用而發(fā)生等待的次數(shù)(不能立即獲取鎖的次數(shù)携兵, 每等待一次鎖值加1),此值高則說(shuō)明存在著較嚴(yán)重的表級(jí)鎖定爭(zhēng)用情況搂誉;
3.mysql 事務(wù)
1).事務(wù)及其ACID屬性
A(Atomicity [?t?m??s?ti])代表原子性徐紧,即事務(wù)是一個(gè)原子操作單元,對(duì)數(shù)據(jù)的修改炭懊,要么全部執(zhí)行并级,要么一個(gè)都不執(zhí)行;
C(Consistency [k?n?s?st?nsi] )代表一致性,在事務(wù)開(kāi)始和完成時(shí)凛虽,數(shù)據(jù)都必須保持一致?tīng)顟B(tài)死遭。這意味著所有相關(guān)的數(shù)據(jù)規(guī)則都必須應(yīng)用于事務(wù)的修改,以保持?jǐn)?shù)據(jù)的完整性凯旋;事務(wù)結(jié)束時(shí),所有的內(nèi)部數(shù)據(jù)結(jié)構(gòu)(如B樹(shù)索引或雙向鏈表)也必須是正確的钉迷。
如轉(zhuǎn)賬業(yè)務(wù)至非,無(wú)論事務(wù)執(zhí)行成功與否,參與轉(zhuǎn)賬的兩個(gè)賬號(hào)余額之和應(yīng)該是不變的糠聪。
I(Isolation [a?s??le??n] )代表隔離性荒椭,即兩個(gè)事務(wù)不會(huì)相互影響、覆蓋彼此數(shù)據(jù)等;
D(Durability [dj??r?'b?l?t?])表示持久化舰蟆,即一旦事務(wù)提交后趣惠,它所做的修改將會(huì)永久的保存在數(shù)據(jù)庫(kù)上,即使出現(xiàn)宕機(jī)也不會(huì)丟失身害。
2).并發(fā)事務(wù)處理帶來(lái)的問(wèn)題
對(duì)于同時(shí)運(yùn)行的多個(gè)事務(wù), 當(dāng)這些事務(wù)訪問(wèn)數(shù)據(jù)庫(kù)中相同的數(shù)據(jù)時(shí), 如果沒(méi)有采取必要的隔離機(jī)制, 就會(huì)導(dǎo)致各種并發(fā)問(wèn)題:
a).更新丟失: 當(dāng)兩個(gè)或多個(gè)事務(wù)選擇同一行味悄,然后基于選定的值更新該行時(shí),由于每個(gè)事務(wù)都不知道其他事務(wù)的存在塌鸯,就會(huì)發(fā)生丟失更新問(wèn)題:最后的更新覆蓋了由其他事務(wù)所做的更新侍瑟。
b).臟讀: 對(duì)于兩個(gè)事物 T1, T2, T1 讀取了已經(jīng)被 T2 更新但尚未提交 的字段. 之后, 若 T2 回滾, T1讀取的內(nèi)容就是臨時(shí)且無(wú)效的。也就是讀取了其他事務(wù)還沒(méi)有提交的數(shù)據(jù)
c).不可重復(fù)讀: 對(duì)于兩個(gè)事物 T1, T2, T1 讀取了一個(gè)字段, 然后 T2 更新了該字段. 之后, T1再次讀取同一個(gè)字段, 值就不同了。當(dāng)前事務(wù)已經(jīng)讀取的數(shù)據(jù)記錄涨颜,被其他事務(wù)修改或刪除费韭。
d).幻讀: 對(duì)于兩個(gè)事物 T1, T2, T1從一個(gè)表中讀取了一個(gè)字段, 然后T2在該表中 新增了 一些新的行. 之后, 如果T1再讀取同一個(gè)表, 就會(huì)多出幾行。其他事務(wù)插入了新的數(shù)據(jù)庭瑰,當(dāng)前事務(wù)以相同的查詢(xún)條件星持,在那個(gè)事務(wù)插入數(shù)據(jù)之前和之后查詢(xún)數(shù)據(jù),得到的數(shù)據(jù)條數(shù)不一樣
3).事務(wù)隔離級(jí)別
隔離級(jí)別(isolation level)弹灭,是指事務(wù)與事務(wù)之間的隔離程度督暂。
顯然,事務(wù)隔離程度越高鲤屡,并發(fā)性越差损痰、性能越低;事務(wù)隔離程度越低酒来,并發(fā)性越強(qiáng)卢未、性能越高。
Mysql 支持4種事務(wù)隔離級(jí)別. Mysql 默認(rèn)的事務(wù)隔離級(jí)別為: REPEATABLE READ
a).read uncommitted(讀未提交數(shù)據(jù)):允許事務(wù)讀取未被其他事物提交的變更堰汉。臟讀辽社、不可重復(fù)讀和幻讀的問(wèn)題都會(huì)出現(xiàn)
b).read commited(讀已提交數(shù)據(jù)):只允許事務(wù)讀取已經(jīng)被其他事務(wù)提交的變更∏萄迹可避免臟讀滴铅,但不可重復(fù)讀和幻讀問(wèn)題仍然存在
c).repeatable read(可重復(fù)讀):確保事務(wù)可以多次從一個(gè)字段中讀取相同的值,在這個(gè)事務(wù)持續(xù)期間就乓,禁止其他事務(wù)對(duì)這個(gè)字段進(jìn)行更新汉匙。可避免臟讀和不可重復(fù)讀生蚁,但幻讀的問(wèn)題仍然存在
d).serializable(串行化):確保事務(wù)可以從一個(gè)表中讀取相同的行噩翠,在這個(gè)事務(wù)持續(xù)期間,禁止其他事務(wù)對(duì)該表執(zhí)行增刪改操作邦投。所有的并發(fā)問(wèn)題都可以避免伤锚,但性能什么低下。
查看當(dāng)前數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別:show variables like 'tx_isolation'
4.樂(lè)觀鎖&悲觀鎖
悲觀鎖
悲觀鎖(Pessimistic Lock)志衣,顧名思義屯援,就是很悲觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改念脯,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖狞洋,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)block直到它拿到鎖。
悲觀鎖:假定會(huì)發(fā)生并發(fā)沖突和二,屏蔽一切可能違反數(shù)據(jù)完整性的操作徘铝。
Java synchronized 就屬于悲觀鎖的一種實(shí)現(xiàn),每次線程要修改數(shù)據(jù)時(shí)都先獲得鎖,保證同一時(shí)刻只有一個(gè)線程能操作數(shù)據(jù)惕它,其他線程則會(huì)被block怕午。
優(yōu)點(diǎn)與不足
悲觀并發(fā)控制實(shí)際上是“先取鎖再訪問(wèn)”的保守策略,為數(shù)據(jù)處理的安全提供了保證淹魄。但是在效率方面郁惜,處理加鎖的機(jī)制會(huì)讓數(shù)據(jù)庫(kù)產(chǎn)生額外的開(kāi)銷(xiāo),還有增加產(chǎn)生死鎖的機(jī)會(huì)甲锡;另外兆蕉,在只讀型事務(wù)處理中由于不會(huì)產(chǎn)生沖突,也沒(méi)必要使用鎖缤沦,這樣做只能增加系統(tǒng)負(fù)載虎韵;還有會(huì)降低了并行性,一個(gè)事務(wù)如果鎖定了某行數(shù)據(jù)缸废,其他事務(wù)就必須等待該事務(wù)處理完才可以處理那行數(shù)包蓝。
樂(lè)觀鎖
樂(lè)觀鎖(Optimistic Lock),顧名思義企量,就是很樂(lè)觀测萎,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖届巩,但是在提交更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù)硅瞧。樂(lè)觀鎖適用于讀多寫(xiě)少的應(yīng)用場(chǎng)景,這樣可以提高吞吐量恕汇。
樂(lè)觀鎖:假設(shè)不會(huì)發(fā)生并發(fā)沖突腕唧,只在提交操作時(shí)檢查是否違反數(shù)據(jù)完整性。
樂(lè)觀鎖一般來(lái)說(shuō)有以下2種方式:
- 使用數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn)瘾英,這是樂(lè)觀鎖最常用的一種實(shí)現(xiàn)方式四苇。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí)方咆,一般是通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè)數(shù)字類(lèi)型的 “version” 字段來(lái)實(shí)現(xiàn)。當(dāng)讀取數(shù)據(jù)時(shí)蟀架,將version字段的值一同讀出瓣赂,數(shù)據(jù)每更新一次,對(duì)此version值加一片拍。當(dāng)我們提交更新的時(shí)候煌集,判斷數(shù)據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息與第一次取出來(lái)的version值進(jìn)行比對(duì),如果數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào)與第一次取出來(lái)的version值相等捌省,則予以更新苫纤,否則認(rèn)為是過(guò)期數(shù)據(jù)。
- 使用時(shí)間戳(timestamp)。樂(lè)觀鎖定的第二種實(shí)現(xiàn)方式和第一種差不多卷拘,同樣是在需要樂(lè)觀鎖控制的table中增加一個(gè)字段喊废,名稱(chēng)無(wú)所謂,字段類(lèi)型使用時(shí)間戳(timestamp), 和上面的version類(lèi)似栗弟,也是在更新提交的時(shí)候檢查當(dāng)前數(shù)據(jù)庫(kù)中數(shù)據(jù)的時(shí)間戳和自己更新前取到的時(shí)間戳進(jìn)行對(duì)比污筷,如果一致則OK,否則就是版本沖突乍赫。
Java JUC中的atomic包就是樂(lè)觀鎖的一種實(shí)現(xiàn)瓣蛀,AtomicInteger 通過(guò)CAS(Compare And Set)操作實(shí)現(xiàn)線程安全的自增。
優(yōu)點(diǎn)與不足
樂(lè)觀并發(fā)控制相信事務(wù)之間的數(shù)據(jù)競(jìng)爭(zhēng)(data race)的概率是比較小的雷厂,因此盡可能直接做下去惋增,直到提交的時(shí)候才去鎖定,所以不會(huì)產(chǎn)生任何鎖和死鎖改鲫。但如果直接簡(jiǎn)單這么做诈皿,還是有可能會(huì)遇到不可預(yù)期的結(jié)果,例如兩個(gè)事務(wù)都讀取了數(shù)據(jù)庫(kù)的某一行钩杰,經(jīng)過(guò)修改以后寫(xiě)回?cái)?shù)據(jù)庫(kù)纫塌,這時(shí)就遇到了問(wèn)題。
MySQL隱式和顯示鎖定
MySQL InnoDB采用的是兩階段鎖定協(xié)議(two-phase locking protocol)讲弄。在事務(wù)執(zhí)行過(guò)程中措左,隨時(shí)都可以執(zhí)行鎖定,鎖只有在執(zhí)行 COMMIT或者ROLLBACK的時(shí)候才會(huì)釋放避除,并且所有的鎖是在同一時(shí)刻被釋放怎披。前面描述的鎖定都是隱式鎖定,InnoDB會(huì)根據(jù)事務(wù)隔離級(jí)別在需要的時(shí)候自動(dòng)加鎖瓶摆。
另外凉逛,InnoDB也支持通過(guò)特定的語(yǔ)句進(jìn)行顯示鎖定,這些語(yǔ)句不屬于SQL規(guī)范:
- SELECT ... LOCK IN SHARE MODE
- SELECT ... FOR UPDATE
案例分析(來(lái)自網(wǎng)絡(luò)):
初始情況:多線程并發(fā)情況下群井,會(huì)存在超賣(mài)的可能状飞。
/**
* 更新庫(kù)存(不考慮并發(fā))
* @param productId
* @return
*/
public boolean updateStockRaw(Long productId){
ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId}", productId);
if (product.getNumber() > 0) {
int updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId}", productId);
if(updateCnt > 0){ //更新庫(kù)存成功
return true;
}
}
return false;
}
悲觀鎖:
/**
* 更新庫(kù)存(使用悲觀鎖)
* @param productId
* @return
*/
public boolean updateStock(Long productId){
//先鎖定商品庫(kù)存記錄
ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId} FOR UPDATE", productId);
if (product.getNumber() > 0) {
int updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId}", productId);
if(updateCnt > 0){ //更新庫(kù)存成功
return true;
}
}
return false;
}
樂(lè)觀鎖:使用樂(lè)觀鎖更新庫(kù)存的時(shí)候不加鎖,當(dāng)提交更新時(shí)需要判斷數(shù)據(jù)是否已經(jīng)被修改(AND number=#{number})书斜,只有在 number等于上一次查詢(xún)到的number時(shí) 才提交更新诬辈。
** 注意** :
1.UPDATE 語(yǔ)句的WHERE 條件字句上需要建索引;
2.樂(lè)觀鎖的實(shí)現(xiàn)荐吉,如果ORM使用JPA焙糟,springboot中有version注解可以實(shí)現(xiàn);如果ORM使用Mybatis样屠,有插件可以實(shí)現(xiàn)穿撮;詳細(xì)的下一篇文章整理缺脉。
/**
* 下單減庫(kù)存
* @param productId
* @return
*/
public boolean updateStock(Long productId){
int updateCnt = 0;
while (updateCnt == 0) {
ProductStock product = query("SELECT * FROM tb_product_stock WHERE product_id=#{productId}", productId);
if (product.getNumber() > 0) {
updateCnt = update("UPDATE tb_product_stock SET number=number-1 WHERE product_id=#{productId} AND number=#{number}", productId, product.getNumber());
if(updateCnt > 0){ //更新庫(kù)存成功
return true;
}
} else { //賣(mài)完啦
return false;
}
}
return false;
}
5.死鎖
在數(shù)據(jù)庫(kù)中有兩種基本的鎖類(lèi)型:排它鎖(Exclusive Locks,即X鎖)和共享鎖(Share Locks悦穿,即S鎖)攻礼。當(dāng)數(shù)據(jù)對(duì)象被加上排它鎖時(shí),其他的事務(wù)不能對(duì)它讀取和修改咧党。加了共享鎖的數(shù)據(jù)對(duì)象可以被其他事務(wù)讀取秘蛔,但不能修改。數(shù)據(jù)庫(kù)利用這兩 種基本的鎖類(lèi)型來(lái)對(duì)數(shù)據(jù)庫(kù)的事務(wù)進(jìn)行并發(fā)控制傍衡。
5.1.死鎖的產(chǎn)生情況
5.1.1死鎖的第一種情況
一個(gè)用戶A 訪問(wèn)表A(鎖住了表A),然后又訪問(wèn)表B深员;另一個(gè)用戶B 訪問(wèn)表B(鎖住了表B),然后企圖訪問(wèn)表A蛙埂;這時(shí)用戶A由于用戶B已經(jīng)鎖住表B倦畅,它必須等待用戶B釋放表B才能繼續(xù),同樣用戶B要等用戶A釋放表A才能繼續(xù)绣的,這就死鎖就產(chǎn)生了叠赐。
解決方法:
這種死鎖比較常見(jiàn),是由于程序的BUG產(chǎn)生的屡江,除了調(diào)整的程序的邏輯沒(méi)有其它的辦法芭概。仔細(xì)分析程序的邏輯,對(duì)于數(shù)據(jù)庫(kù)的多表操作時(shí)惩嘉,盡量按照相同的順序進(jìn) 行處理罢洲,盡量避免同時(shí)鎖定兩個(gè)資源,如操作A和B兩張表時(shí)文黎,總是按先A后B的順序處理惹苗, 必須同時(shí)鎖定兩個(gè)資源時(shí),要保證在任何時(shí)刻都應(yīng)該按照相同的順序來(lái)鎖定資源耸峭。
5.1.2死鎖的第二種情況
用戶A查詢(xún)一條紀(jì)錄桩蓉,然后修改該條紀(jì)錄;這時(shí)用戶B修改該條紀(jì)錄劳闹,這時(shí)用戶A的事務(wù)里鎖的性質(zhì)由查詢(xún)的共享鎖企圖上升到獨(dú)占鎖院究,而用戶B里的獨(dú)占鎖由于A 有共享鎖存在所以必須等A釋放掉共享鎖,而A由于B的獨(dú)占鎖而無(wú)法上升的獨(dú)占鎖也就不可能釋放共享鎖本涕,于是出現(xiàn)了死鎖儡首。這種死鎖比較隱蔽,但在稍大點(diǎn)的項(xiàng) 目中經(jīng)常發(fā)生偏友。如在某項(xiàng)目中,頁(yè)面上的按鈕點(diǎn)擊后对供,沒(méi)有使按鈕立刻失效位他,使得用戶會(huì)多次快速點(diǎn)擊同一按鈕氛濒,這樣同一段代碼對(duì)數(shù)據(jù)庫(kù)同一條記錄進(jìn)行多次操 作,很容易就出現(xiàn)這種死鎖的情況鹅髓。
解決方法:
1舞竿、對(duì)于按鈕等控件,點(diǎn)擊后使其立刻失效窿冯,不讓用戶重復(fù)點(diǎn)擊骗奖,避免對(duì)同時(shí)對(duì)同一條記錄操作。
2醒串、使用樂(lè)觀鎖進(jìn)行控制执桌。樂(lè)觀鎖大多是基于數(shù)據(jù)版本(Version)記錄機(jī)制實(shí)現(xiàn)。即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí)芜赌,在基于數(shù)據(jù)庫(kù)表的版本解決方案中仰挣,一般是 通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè)“version”字段來(lái)實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí)缠沈,將此版本號(hào)一同讀出膘壶,之后更新時(shí),對(duì)此版本號(hào)加一洲愤。此時(shí)颓芭,將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù) 據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào)柬赐,則予以更新亡问,否則認(rèn)為是過(guò)期數(shù)據(jù)。樂(lè)觀鎖機(jī)制避免了長(zhǎng)事務(wù)中的數(shù)據(jù) 庫(kù)加鎖開(kāi)銷(xiāo)(用戶A和用戶B操作過(guò)程中躺率,都沒(méi)有對(duì)數(shù)據(jù)庫(kù)數(shù)據(jù)加鎖)玛界,大大提升了大并發(fā)量下的系統(tǒng)整體性能表現(xiàn)。Hibernate 在其數(shù)據(jù)訪問(wèn)引擎中內(nèi)置了樂(lè)觀鎖實(shí)現(xiàn)悼吱。需要注意的是慎框,由于樂(lè)觀鎖機(jī)制是在我們的系統(tǒng)中實(shí)現(xiàn),來(lái)自外部系統(tǒng)的用戶更新操作不受我們系統(tǒng)的控制后添,因此可能會(huì)造 成臟數(shù)據(jù)被更新到數(shù)據(jù)庫(kù)中笨枯。
3、使用悲觀鎖進(jìn)行控制遇西。悲觀鎖大多數(shù)情況下依靠數(shù)據(jù)庫(kù)的鎖機(jī)制實(shí)現(xiàn)馅精,如Oracle的Select … for update語(yǔ)句,以保證操作最大程度的獨(dú)占性粱檀。但隨之而來(lái)的就是數(shù)據(jù)庫(kù)性能的大量開(kāi)銷(xiāo)洲敢,特別是對(duì)長(zhǎng)事務(wù)而言,這樣的開(kāi)銷(xiāo)往往無(wú)法承受茄蚯。如一個(gè)金融系統(tǒng)压彭, 當(dāng)某個(gè)操作員讀取用戶的數(shù)據(jù)睦优,并在讀出的用戶數(shù)據(jù)的基礎(chǔ)上進(jìn)行修改時(shí)(如更改用戶賬戶余額),如果采用悲觀鎖機(jī)制壮不,也就意味著整個(gè)操作過(guò)程中(從操作員讀 出數(shù)據(jù)汗盘、開(kāi)始修改直至提交修改結(jié)果的全過(guò)程,甚至還包括操作員中途去煮咖啡的時(shí)間)询一,數(shù)據(jù)庫(kù)記錄始終處于加鎖狀態(tài)隐孽,可以想見(jiàn),如果面對(duì)成百上千個(gè)并發(fā)健蕊,這 樣的情況將導(dǎo)致災(zāi)難性的后果菱阵。所以,采用悲觀鎖進(jìn)行控制時(shí)一定要考慮清楚绊诲。
5.1.3死鎖的第三種情況
如果在事務(wù)中執(zhí)行了一條不滿足條件的update語(yǔ)句送粱,則執(zhí)行全表掃描,把行級(jí)鎖上升為表級(jí)鎖掂之,多個(gè)這樣的事務(wù)執(zhí)行后抗俄,就很容易產(chǎn)生死鎖和阻塞。類(lèi)似的情 況還有當(dāng)表中的數(shù)據(jù)量非常龐大而索引建的過(guò)少或不合適的時(shí)候世舰,使得經(jīng)常發(fā)生全表掃描动雹,最終應(yīng)用系統(tǒng)會(huì)越來(lái)越慢,最終發(fā)生阻塞或死鎖跟压。
5.2.解除正在死鎖的狀態(tài)有兩種方法:
第一種:
1.查詢(xún)是否鎖表
show OPEN TABLES where In_use > 0;
2.查詢(xún)進(jìn)程(如果您有SUPER權(quán)限胰蝠,您可以看到所有線程。否則震蒋,您只能看到您自己的線程)
show processlist
3.殺死進(jìn)程id(就是上面命令的id列)
kill id
第二種:
1.查看下在鎖的事務(wù)
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2.殺死進(jìn)程id(就是上面命令的trx_mysql_thread_id列)
kill 線程ID
其它關(guān)于查看死鎖的命令:
1:查看當(dāng)前的事務(wù)
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2:查看當(dāng)前鎖定的事務(wù)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:查看當(dāng)前等鎖的事務(wù)
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
5.3查看死鎖日志:
SHOW ENGINE INNODB STATUS;
閱讀死鎖日志
遇到死鎖茸塞,第一步就是閱讀死鎖日志。
死鎖日志通常分為兩部分查剖,上半部分說(shuō)明了事務(wù)1在等待什么鎖钾虐;
然后日志的下半部分說(shuō)明了事務(wù)2當(dāng)前持有的鎖以及等待的鎖;
感謝網(wǎng)友的分享(不全請(qǐng)見(jiàn)諒):
https://blog.csdn.net/hotdust/article/details/51524469
https://blog.csdn.net/hj7jay/article/details/56274056
……