線上出現(xiàn)死鎖怎么解決?
為什么要學(xué)習(xí)鎖機(jī)制
鎖是計(jì)算機(jī)協(xié)調(diào)多個(gè)進(jìn)程或線程并發(fā)訪問某一資源的機(jī)制。
因?yàn)閿?shù)據(jù)也是一種供許多用戶共享的資源,如何保證數(shù)據(jù)并發(fā)訪問的一致性、有效性是所有數(shù)據(jù)庫必須解決的一個(gè)問題,鎖沖突也是影響數(shù)據(jù)庫并發(fā)訪問性能的一個(gè)重要因素,所以進(jìn)一步學(xué)習(xí)MySQL,就需要去了解它的鎖機(jī)制。
本文主要記錄學(xué)習(xí)了 MyISAM 和 InnoDB 這兩個(gè)存儲(chǔ)引擎,而且更加關(guān)注的是 InnoDB(因?yàn)榻?jīng)常用??)
MySQL鎖概述:
相對其他數(shù)據(jù)庫而言,MySQL 的鎖機(jī)制比較簡單,其最顯著的特點(diǎn)是不同的存儲(chǔ)引擎支持不同的鎖機(jī)制。比如,MyISAM和MEMORY存儲(chǔ)引擎采用的是表級鎖(table-level locking);BDB存儲(chǔ)引擎采用的是頁面鎖(page-level locking),但也支持表級鎖;InnoDB存儲(chǔ)引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認(rèn)情況下是采用行級鎖。MySQL這3種鎖的特性可大致歸納如下。
開銷、加鎖速度、死鎖、粒度、并發(fā)性能 ①:表級鎖:開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。
②:行級鎖:開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。
③:頁面鎖:開銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。
從上述特點(diǎn)可見,很難籠統(tǒng)地說哪種鎖更好,只能就具體應(yīng)用的特點(diǎn)來說哪種鎖更合適!僅從鎖的角度來說:表級鎖更適合于以查詢?yōu)橹鳎挥猩倭堪此饕龡l件更新數(shù)據(jù)的應(yīng)用,如Web應(yīng)用;而行級鎖則更適合于有大量按索引條件并發(fā)更新少量不同數(shù)據(jù),同時(shí)又有并發(fā)查詢的應(yīng)用,如一些在線事務(wù)處理(OLTP)系統(tǒng)。由于BDB已經(jīng)被InnoDB取代,即將成為歷史(所以現(xiàn)在基本都在使用InnoDB存儲(chǔ)引擎)。
MyISAN存儲(chǔ)引擎
MyISAM 存儲(chǔ)引擎只支持表鎖,這也是 MySQL 開始幾個(gè)版本中唯一支持的鎖類型。
MySQL表級鎖
查詢表鎖爭用情況
mysql> show status like 'table%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Table_locks_immediate | 4 |
| Table_locks_waited | 0 |
| Table_open_cache_hits | 4 |
| Table_open_cache_misses | 8 |
| Table_open_cache_overflows | 0 |
+----------------------------+-------+
5 rows in set (0.00 sec)
如果 Table_locks_waited 的值比較高,則說明存在著較嚴(yán)重的表級鎖爭用情況。
MySQL的表級鎖的兩種模式
表共享讀鎖(Table Read Lock) 表獨(dú)占寫鎖(Table Write Lock)
MySQL中的表鎖兼容性:
| 請求鎖模式 矩陣結(jié)果表示是否兼容 當(dāng)前鎖模式 | None | 讀鎖 | 寫鎖 |
|---|---|---|---|
| 讀鎖 | 是 | 是 | 否 |
| 寫鎖 | 是 | 否 | 否 |
也就是說,在MyISAM讀模式下,不會(huì)阻塞其它用戶的同一表讀操作,但是會(huì)阻塞寫操作;而在寫模式下,會(huì)同時(shí)阻塞其它用戶同一表的讀寫操作。
測試MyISAM的寫鎖模式
新建一個(gè)user表,引擎是MyISAM:
mysql> desc user;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| age | int(3) | YES | | NULL | |
| address | varchar(60) | YES | | NULL | |
+---------+-------------+------+-----+---------+----------------+
4 rows in set (0.01 sec)
| session A | session B |
|---|---|
| 獲得user表的鎖鎖定 mysql> lock table user write; Query OK, 0 rows affected (0.00 sec) mysql>select * from user; Empty set (0.00 sec) mysql> insert into user(id, name, age, address) values(1, ‘test’, 18, ‘test address’); Query OK,1 row affected (0.02 sec) | |
| mysql> select * from user\G 被阻塞了,一直卡住在這,沒有返回結(jié)果 | |
| mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) | 等待 |
| mysql> select from user\G *** name: test age: 18 address: test address 1 row in set (5 min 29.61 sec) |
可以看出,通過lock table user write將user表鎖住后,其它用戶進(jìn)行對該表操作時(shí),都會(huì)被阻塞。
測試MyISAM讀鎖
在用LOCK TABLES給表顯式加表鎖時(shí),必須同時(shí)取得所有涉及到表的鎖,并且MySQL不支持鎖升級。也就是說,在執(zhí)行LOCK TABLES后,只能訪問顯式加鎖的這些表,不能訪問未加鎖的表;同時(shí),如果加的是讀鎖,那么只能執(zhí)行查詢操作,而不能執(zhí)行更新操作。其實(shí),在自動(dòng)加鎖的情況下也基本如此,MyISAM總是一次獲得SQL語句所需要的全部鎖。這也正是MyISAM表不會(huì)出現(xiàn)死鎖(Deadlock Free)的原因。
| session A | session B |
|---|---|
| 獲得user表的讀鎖定 mysql> lock table user read; Query OK, 0 rows affected (0.00 sec) | |
| mysql> select * from user where id = 1 \G 中從查詢速度中可以看出,sessionB并沒有被阻塞 1 row in set (0.00 sec) | |
由于沒有獲取order表的讀鎖定,所以不能查詢order表 mysql> select * from order; ERROR 1100 (HY000): Table ‘order’ was not locked with LOCK TABLES | 但是session B可以訪問oder表,不阻塞 mysql> select * from order; Empty set (0.00 sec) |
| 獲得讀鎖定時(shí),不能進(jìn)行寫操作 mysql> update user set name = ‘wahaha’ where id = 1; ERROR 1099 (HY000): Table ‘user’ was locked with a READ lock and can’t be updated | 其它session進(jìn)行更新操作時(shí),會(huì)被阻塞 mysql> update user set name = ‘wahaha’ where id = 1; 等待ing |
| 釋放鎖 mysql> unlock tables; Query OK, 0 rows affected (0.00 sec) | 等待 |
| mysql> update user set name = ‘wahaha’ where id = 1; Query OK, 1 row affected (1 min 6.43 sec) |
MyISAM支持并發(fā)插入
MyISAM表的讀和寫是串行的,但這是就總體而言的。在一定條件下,MyISAM表也支持查詢和插入操作的并發(fā)進(jìn)行。MyISAM存儲(chǔ)引擎有一個(gè)系統(tǒng)變量concurrent_insert,專門用以控制其并發(fā)插入的行為,其值分別可以為0、1或2。
當(dāng)concurrent_insert設(shè)置為0時(shí),不允許并發(fā)插入。 當(dāng)concurrent_insert設(shè)置為1時(shí),如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個(gè)進(jìn)程讀表的同時(shí),另一個(gè)進(jìn)程從表尾插入記錄。這也是MySQL的默認(rèn)設(shè)置。 當(dāng)concurrent_insert設(shè)置為2時(shí),無論MyISAM表中有沒有空洞,都允許在表尾并發(fā)插入記錄。
MyISAM的鎖調(diào)度
MyISAM存儲(chǔ)引擎的讀鎖和寫鎖是互斥的,讀寫操作是串行的。但它認(rèn)為寫鎖的優(yōu)先級比讀鎖高,所以即使讀請求先到鎖等待隊(duì)列,寫請求后到,寫鎖也會(huì)插到讀鎖請求之前!這也正是MyISAM表不太適合于有大量更新操作和查詢操作應(yīng)用的原因,因?yàn)椋罅康母虏僮鲿?huì)造成查詢操作很難獲得讀鎖,從而可能永遠(yuǎn)阻塞。可以通過一些設(shè)置來調(diào)節(jié)MyISAM的調(diào)度行為。
通過指定啟動(dòng)參數(shù)low-priority-updates,使MyISAM引擎默認(rèn)給予讀請求以優(yōu)先的權(quán)利。 通過執(zhí)行命令SET LOW_PRIORITY_UPDATES=1,使該連接發(fā)出的更新請求優(yōu)先級降低。 通過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優(yōu)先級。
雖然上面3種方法都是要么更新優(yōu)先,要么查詢優(yōu)先的方法,但還是可以用其來解決查詢相對重要的應(yīng)用(如用戶登錄系統(tǒng))中,讀鎖等待嚴(yán)重的問題。另外,MySQL也提供了一種折中的辦法來調(diào)節(jié)讀寫沖突,即給系統(tǒng)參數(shù)max_write_lock_count設(shè)置一個(gè)合適的值,當(dāng)一個(gè)表的讀鎖達(dá)到這個(gè)值后,MySQL就暫時(shí)將寫請求的優(yōu)先級降低,給讀進(jìn)程一定獲得鎖的機(jī)會(huì)。上面已經(jīng)討論了寫優(yōu)先調(diào)度機(jī)制帶來的問題和解決辦法。這里還要強(qiáng)調(diào)一點(diǎn):一些需要長時(shí)間運(yùn)行的查詢操作,也會(huì)使寫進(jìn)程“餓死”!因此,應(yīng)用中應(yīng)盡量避免出現(xiàn)長時(shí)間運(yùn)行的查詢操作,不要總想用一條SELECT語句來解決問題,因?yàn)檫@種看似巧妙的SQL語句,往往比較復(fù)雜,執(zhí)行時(shí)間較長,在可能的情況下可以通過使用中間表等措施對SQL語句做一定的“分解”,使每一步查詢都能在較短時(shí)間完成,從而減少鎖沖突。如果復(fù)雜查詢不可避免,應(yīng)盡量安排在數(shù)據(jù)庫空閑時(shí)段執(zhí)行,比如一些定期統(tǒng)計(jì)可以安排在夜間執(zhí)行。
InnoDB
InnoDB與MyISAM的最大不同有兩點(diǎn):一是支持事務(wù)(TRANSACTION);二是采用了行級鎖。行級鎖與表級鎖本來就有許多不同之處,另外,事務(wù)的引入也帶來了一些新問題。
事務(wù)概念
學(xué)習(xí)Spring的時(shí)候,一般通過注解@Transitional就能啟動(dòng)spring的事務(wù)管理,在MySQL中也同樣支持事務(wù)的四個(gè)原則ACID:
A(Atomicity)原子性: 事務(wù)是一個(gè)原子操作單元,其對數(shù)據(jù)的修改,要么全都執(zhí)行,要么全都不執(zhí)行。 C(Consistent)一致性: 在事務(wù)開始和完成時(shí),數(shù)據(jù)都必須保持一致狀態(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樹索引或雙向鏈表)也都必須是正確的。 I(Isolation)隔離性: 數(shù)據(jù)庫系統(tǒng)提供一定的隔離機(jī)制,保證事務(wù)在不受外部并發(fā)操作影響的“獨(dú)立”環(huán)境執(zhí)行。這意味著事務(wù)處理過程中的中間狀態(tài)對外部是不可見的,反之亦然。 D(Durable)持久性: 事務(wù)完成之后,它對于數(shù)據(jù)的修改是永久性的,即使出現(xiàn)系統(tǒng)故障也能夠保持。
并發(fā)事務(wù)處理帶來的問題
相對于串行處理來說,并發(fā)事務(wù)處理能大大增加數(shù)據(jù)庫資源的利用率,提高數(shù)據(jù)庫系統(tǒng)的事務(wù)吞吐量,從而可以支持更多的用戶。但并發(fā)事務(wù)處理也會(huì)帶來一些問題,主要包括以下幾種情況。
更新丟失(Last update):A和B同時(shí)對一行數(shù)據(jù)進(jìn)行處理,A修改后進(jìn)行保存,然后B修改后進(jìn)行保存,這樣A的更新被覆蓋了,相當(dāng)于發(fā)生丟失更新的問題。所以可以在A事務(wù)未結(jié)束前,B不能訪問該記錄,這樣就能避免更新丟失的問題。 臟讀(Dirty Reads):A事務(wù)在對一條記錄做修改,但還未提交,這條記錄處于不一致的狀態(tài);這時(shí),B事務(wù)也來讀同一條記錄,這時(shí)如果沒有加控制,B讀了未修改前的數(shù)據(jù),并根據(jù)該數(shù)據(jù)進(jìn)行進(jìn)一步處理,就會(huì)產(chǎn)生未提交的數(shù)據(jù)依賴關(guān)系。這種現(xiàn)象叫做“臟讀” 不可重復(fù)讀(Non-Repeatable Reads):B事務(wù)在讀取某些數(shù)據(jù)后的某個(gè)時(shí)間,再次讀取以前讀過的數(shù)據(jù),卻發(fā)現(xiàn)其讀出的數(shù)據(jù)已經(jīng)發(fā)生了改變(被更新或者刪除了,例如A事務(wù)修改了)。這種現(xiàn)象叫做“不可重復(fù)讀”。 幻讀(Phantom Reads):A事務(wù)按照相同查詢條件,重新讀取之前檢索過得內(nèi)容,卻發(fā)現(xiàn)其它事務(wù)插入或修改其查詢條件的新數(shù)據(jù),這種現(xiàn)象就叫”幻讀“。
事務(wù)的隔離級別
數(shù)據(jù)庫的事務(wù)隔離越嚴(yán)格,并發(fā)副作用越小,但付出的代價(jià)也就越大,因?yàn)槭聞?wù)隔離實(shí)質(zhì)上就是使事務(wù)在一定程度上 “串行化”進(jìn)行,這顯然與“并發(fā)”是矛盾的。同時(shí),不同的應(yīng)用對讀一致性和事務(wù)隔離程度的要求也是不同的,比如許多應(yīng)用對“不可重復(fù)讀”和“幻讀”并不敏感,可能更關(guān)心數(shù)據(jù)并發(fā)訪問的能力。
4種隔離級別比較
| 讀數(shù)據(jù)一致性及允許的并發(fā)副作用 隔離級別 | 讀數(shù)據(jù)一致性 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|---|
| 未提交讀(Read uncommitted) | 最低級別,只能保證不讀取 物理上損害的數(shù)據(jù) | 是 | 是 | 是 |
| 已提交讀(Read committed) | 語句級 | 否 | 是 | 是 |
| 可重復(fù)讀(Repeatable read) | 事務(wù)級 | 否 | 否 | 是 |
| 可序列化(Serializable) | 最高級別,事務(wù)級 | 否 | 否 | 否 |
獲取InnoDB行鎖爭用情況
檢查InnoDB_row_lock狀態(tài)變量來分析:
mysql> show status like 'InnoDB_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 rows in set (0.00 sec)
如果InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比較高,表示鎖爭用情況比較嚴(yán)重。
InnoDB的行鎖模式以及加鎖方法
InnoDB實(shí)現(xiàn)了一下兩種類型的行鎖:
共享鎖(S):允許一個(gè)事務(wù)去多一行,阻止其它事務(wù)獲得相同數(shù)據(jù)集的排他鎖。 排他鎖(X): 允許獲得排他鎖的事務(wù)更新數(shù)據(jù),阻止其它事務(wù)獲得相同數(shù)據(jù)集的共享鎖和排他寫鎖。
另外,為了允許行鎖和表鎖共存,實(shí)現(xiàn)多粒度鎖機(jī)制,InnoDB還有兩種內(nèi)部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。(感覺與MyISAM的表鎖機(jī)制類似)
意向共享鎖(IS):事務(wù)打算給數(shù)據(jù)行加行共享鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加共享鎖前必須先取得該表的IS鎖。 意向排他鎖(IX):事務(wù)打算給數(shù)據(jù)行加行排他鎖,事務(wù)在給一個(gè)數(shù)據(jù)行加排他鎖前必須先取得該表的IX鎖。
InnoDB行鎖模式兼容性列表:
| 請求鎖模式 矩陣結(jié)果表示是否兼容 當(dāng)前鎖模式 | X | IX | S | IS |
|---|---|---|---|---|
| X | 沖突 | 沖突 | 沖突 | 沖突 |
| IX | 沖突 | 兼容 | 沖突 | 兼容 |
| S | 沖突 | 沖突 | 兼容 | 兼容 |
| IS | 沖突 | 兼容 | 兼容 | 兼容 |
如果一個(gè)事務(wù)請求的鎖模式與當(dāng)前的鎖兼容,InnoDB就將請求的鎖授予該事務(wù);反之,如果兩者不兼容,該事務(wù)就要等待鎖釋放。意向鎖是InnoDB自動(dòng)加的;對于UPDATE、DELETE和INSERT語句,InnoDB會(huì)自動(dòng)給設(shè)計(jì)數(shù)據(jù)集加排他鎖(X);對于普通的SELECT語句,InnoDB不會(huì)加鎖。可以通過以下語句顯示給記錄集加共享鎖或排他鎖:
共享鎖(S):SELECT * FROM TABLE_NAME WHERE … LOCK IN SHARE MODE. 排他鎖(X):SELECT * FROM TABLE_NAME WHERE … FOR UPDATE.
用SELECT … IN SHARE MODE獲得共享鎖,主要用在需要數(shù)據(jù)依存關(guān)系時(shí)來確認(rèn)某行記錄是否存在,并確保沒有人對這個(gè)記錄進(jìn)行UPDATE或者DELETE操作。但是如果當(dāng)前事務(wù)也需要對該記錄進(jìn)行更新操作,則很有可能造成死鎖,對于鎖定行記錄后需要進(jìn)行更新操作的應(yīng)用,應(yīng)該使用SELECT… FOR UPDATE方式獲得排他鎖。
所以在使用共享鎖模式下,查詢完數(shù)據(jù)后不要進(jìn)行更新操作,不然又可能會(huì)造成死鎖;要更新數(shù)據(jù),應(yīng)該使用排他鎖模式。
InnoDB行鎖實(shí)現(xiàn)方式
InnoDB行鎖是通過給索引上的索引項(xiàng)加鎖來實(shí)現(xiàn)的,這一點(diǎn)MySQL與Oracle不同,后者是通過在數(shù)據(jù)塊中對相應(yīng)數(shù)據(jù)行加鎖來實(shí)現(xiàn)的。InnoDB這種行鎖實(shí)現(xiàn)特點(diǎn)意味著:只有通過索引條件檢索數(shù)據(jù),InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!(這個(gè)問題遇到過,由于沒加索引,行鎖變表鎖)
在不通過索引條件查詢的時(shí)候,InnoDB確實(shí)使用的是表鎖,而不是行鎖。 由于MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會(huì)出現(xiàn)鎖沖突的。 當(dāng)表有多個(gè)索引的時(shí)候,不同的事務(wù)可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB都會(huì)使用行鎖來對數(shù)據(jù)加鎖。 即便在條件中使用了索引字段,但是否使用索引來檢索數(shù)據(jù)是由MySQL通過判斷不同執(zhí)行計(jì)劃的代價(jià)來決定的,如果MySQL認(rèn)為全表掃描效率更高,比如對一些很小的表,它就不會(huì)使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。
可以通過explain執(zhí)行計(jì)劃查看是否真正使用了索引。
間隙鎖(Next-key鎖)
當(dāng)我們用范圍條件而不是相等條件檢索數(shù)據(jù),并請求共享或排他鎖時(shí),InnoDB會(huì)給符合條件的已有數(shù)據(jù)記錄的索引項(xiàng)加鎖;對于鍵值在條件范圍內(nèi)但并不存在的記錄,叫做“間隙(GAP)”,InnoDB也會(huì)對這個(gè)“間隙”加鎖,這種鎖機(jī)制就是所謂的間隙鎖(Next-Key鎖)。
舉個(gè)??:
假如emp表中只有101條記錄,其id的值從1~101,下面的sql:select * from emp where id > 100 for update; 是范圍條件查詢,InnoDB不僅會(huì)對符合條件的id值為101的記錄加鎖,也會(huì)對id大于101(并不存在的值)的“間隙”加鎖。
結(jié)論:
很顯然,在使用范圍條件檢索并鎖定記錄時(shí),InnoDB這種加鎖機(jī)制會(huì)阻塞符合條件范圍內(nèi)鍵值的并發(fā)插入,這往往會(huì)造成嚴(yán)重的鎖等待。因此,在實(shí)際應(yīng)用開發(fā)中,尤其是并發(fā)插入比較多的應(yīng)用,我們要盡量優(yōu)化業(yè)務(wù)邏輯,盡量使用相等條件來訪問更新數(shù)據(jù),避免使用范圍條件。
關(guān)于死鎖(DeadLock)
上面知識點(diǎn)說過,MyISAM表鎖是deadlock free的,這是因?yàn)镸yISAM總是一次獲得所需的全部鎖,要么全部滿足,要么等待,因此不會(huì)出現(xiàn)死鎖。但在InnoDB中,除單個(gè)SQL組成的事務(wù)外,鎖是逐步或得的,所以InnoDB發(fā)生死鎖是可能的。
舉個(gè)??:
| session A | session B |
|---|---|
| mysql> set autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> select * from table_1 where where id=1 for update; … 做一些其他處理… | mysql> set autocommit = 0; Query OK, 0 rows affected (0.00 sec) mysql> select * from table_2 where id=1 for update; … |
| select * from table_2 where id =1 for update; 因session_2已取得排他鎖,等待 | 做一些其他處理… |
| mysql> select * from table_1 where where id=1 for update; 死鎖 |
也就是我們死鎖產(chǎn)生的條件,互相持有資源不釋放,還有環(huán)形等待。
發(fā)生死鎖后,InnoDB一般都能自動(dòng)檢測到,并使一個(gè)事務(wù)釋放鎖并回退,另一個(gè)事務(wù)獲得鎖,繼續(xù)完成事務(wù)。但在涉及外部鎖,或涉及表鎖的情況下,InnoDB并不能完全自動(dòng)檢測到死鎖,這需要通過設(shè)置鎖等待超時(shí)參數(shù) innodb_lock_wait_timeout來解決。需要說明的是,這個(gè)參數(shù)并不是只用來解決死鎖問題,在并發(fā)訪問比較高的情況下,如果大量事務(wù)因無法立即獲得所需的鎖而掛起,會(huì)占用大量計(jì)算機(jī)資源,造成嚴(yán)重性能問題,甚至拖跨數(shù)據(jù)庫。我們通過設(shè)置合適的鎖等待超時(shí)閾值,可以避免這種情況發(fā)生。
避免死鎖的方法
在應(yīng)用中,如果不同的程序會(huì)并發(fā)存取多個(gè)表,應(yīng)盡量約定以相同的順序來訪問表,這樣可以大大降低產(chǎn)生死鎖的機(jī)會(huì)。在下面的例子中,由于兩個(gè)session訪問兩個(gè)表的順序不同,發(fā)生死鎖的機(jī)會(huì)就非常高!但如果以相同的順序來訪問,死鎖就可以避免。 在程序以批量方式處理數(shù)據(jù)的時(shí)候,如果事先對數(shù)據(jù)排序,保證每個(gè)線程按固定的順序來處理記錄,也可以大大降低出現(xiàn)死鎖的可能。 在事務(wù)中,如果要更新記錄,應(yīng)該直接申請足夠級別的鎖,即排他鎖,而不應(yīng)先申請共享鎖,更新時(shí)再申請排他鎖,因?yàn)楫?dāng)用戶申請排他鎖時(shí),其他事務(wù)可能又已經(jīng)獲得了相同記錄的共享鎖,從而造成鎖沖突,甚至死鎖。 在REPEATABLE-READ隔離級別下,如果兩個(gè)線程同時(shí)對相同條件記錄用SELECT…FOR UPDATE加排他鎖,在沒有符合該條件記錄情況下,兩個(gè)線程都會(huì)加鎖成功。程序發(fā)現(xiàn)記錄尚不存在,就試圖插入一條新記錄,如果兩個(gè)線程都這么做,就會(huì)出現(xiàn)死鎖。這種情況下,將隔離級別改成READ COMMITTED,就可避免問題。 當(dāng)隔離級別為READ COMMITTED時(shí),如果兩個(gè)線程都先執(zhí)行SELECT…FOR UPDATE,判斷是否存在符合條件的記錄,如果沒有,就插入記錄。此時(shí),只有一個(gè)線程能插入成功,另一個(gè)線程會(huì)出現(xiàn)鎖等待,當(dāng)?shù)?個(gè)線程提交后,第2個(gè)線程會(huì)因主鍵重出錯(cuò),但雖然這個(gè)線程出錯(cuò)了,卻會(huì)獲得一個(gè)排他鎖!這時(shí)如果有第3個(gè)線程又來申請排他鎖,也會(huì)出現(xiàn)死鎖。 對于這種情況,可以直接做插入操作,然后再捕獲主鍵重異常,或者在遇到主鍵重錯(cuò)誤時(shí),總是執(zhí)行ROLLBACK釋放獲得的排他鎖
