近期在處理一個批量表單上傳的優化需求時,遇到一個innoDB的加鎖問題,致使多線程在批量處理表單並插入數據庫數據的時候出現LockTimeOutException
等待鎖超時異常。錯誤異常以下html
org.springframework.dao.CannotAcquireLockException: could not
execute statement; SQL [n/a]; nested exception is
org.hibernate.exception.LockTimeoutException: could not execute
statement...
複製代碼
網上找了不少資料也比較零碎,經過本身對Mysql
以及innodb的理解,結合Mysql官方文檔對Mysql的鎖以及加鎖機制作一次簡單的介紹。mysql
先來回顧一下事務的隔離級別:spring
隔離級別 | Dirty-Read | NonRepeatable-Read | Phantm-Read |
---|---|---|---|
Read-Uncommitted | YES | YES | YES |
Read-Committed | NO | YES | YES |
Repeatable-Read | NO | NO | YES |
Serializable | NO | NO | NO |
表注:不一樣的隔離級別可能致使的讀取問題sql
查看數據庫的隔離級別 mysql> select @@global.tx_isolation, @@session.tx_isolation, @@tx_isolation;
數據庫
InnoDB實現了兩種標準的行級鎖,共享鎖(shared (S) locks) 和排他鎖 (exclusive (X) locks).bash
若是一個事務持有了一個共享鎖,其餘事務仍然能夠獲取這行記錄的共享鎖,但不能獲取到這行記錄的排它鎖。當一個事務獲取到了某一行的排它鎖,則其餘事務將沒法再獲取這行記錄的共享鎖和排它鎖。session
transaction T1 | transaction T2 |
---|---|
begin; | begin; |
update user set name='loy' where id = 1; | update user set name='loy' where id = 1; |
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction | |
commit; |
分析一下上面出現錯誤的緣由,當T1執行 update user set name='loy' where id = 1;
時T1先獲取到X鎖因爲T1沒有執行commit; 因此他一直持有 這把鎖當T2也執行更新操做的時候就會一直等待X致使獲取鎖超時。多線程
意向鎖是一種表級鎖,和基本鎖同樣也分爲共享鎖和排他鎖。併發
事務在獲取共享鎖以前必須先獲取意向共享鎖,同理獲取排他鎖以前必須先獲取意向排它鎖,意向鎖不會阻塞其餘任何對錶的操做,他只是告訴其餘事務他將要去獲取某一行的共享鎖或者排他鎖。性能
X | IX | S | IS | |
---|---|---|---|---|
X | Conflict | Conflict | Conflict | Conflict |
IX | Conflict | Compatible | Conflict | Compatible |
S | Conflict | Conflict | Compatible | Compatible |
IS | Conflict | Compatible | Compatible | Compatible |
注:基本鎖和意向鎖兼容矩陣
記錄是是做用在索引上的一種鎖,他鎖住的是某一條記錄的索引而非記錄自己,若是當前表沒有索引那麼 InnoDB將會爲其建立一個隱藏的聚合索引,而Record Locks
將會鎖住這個隱藏的聚合索引。
間隙鎖和記錄鎖同樣也是做用在索引上。不一樣的是記錄鎖只做用於一條索引記錄而間隙鎖能夠鎖住一個範圍內的索引。舉個例子:
SELECT id from t_user where id BETWEEN 5 AND 10 FOR UPDATE;
間隙鎖將會在(4,5)和(10,11)之間加鎖,他將阻止其餘事務在這個範圍內插入數據。
自增鎖是一種特殊的表級鎖,他只做用在包含自增列的插入操做時。當一個事務正在插入一條數據時,其餘的任何事務都必須等待整個事務完成插入操做,在取獲取鎖來執行插入操做。 官方文檔是這樣描述的: An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.
看到這裏咱們在回到文章開頭的地方,從新思考異常出現的緣由。其實就很明瞭了。在須要保存數據的表中就包含有自動增加的列 id
當多線在同時插入數據時就會致使 AUTO-INC Locks 鎖競爭激烈,當線程等待鎖的時間過長就會有上訴的異常拋出。
併發可能致使的死鎖分析
數據庫建表
CREATE TABLE `user` (
`id` int(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(11) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
複製代碼
T1 | T2 | T3 |
---|---|---|
begin | begin | begin |
INSERT INTO user values(2, 'tom', 10) | INSTERT INTO user values(2, 'tom', 10) | INSTERT INTO user values(2, 'tom', 10) |
rollback; | ||
Query OK, 1 row affected (47.29 sec) | ||
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction |
看一下在T1 roolback以前各事務鎖的佔用狀況
此時T1獲取到的id=2這一行的X排它鎖,T2和T3獲取到的則是S共享鎖,當咱們把T1 rollback
之後T1將釋放以獲取到的X鎖,此時的T2和T3都持有S鎖,回顧S、X鎖的兼容矩陣,由於S鎖和X鎖是衝突的,T2和T3都在等待對方釋放S鎖,致使死鎖。
儘管在平常開發中對InnoDB鎖沒有太多的接觸,可是瞭解InnoDB加鎖機制在遇到相關問題的時候會讓你處理起來更駕輕就熟。InnoDB還有其餘一些比較有意思的鎖感興趣的同窗能夠移步Mysql官方文檔學習。