1. 程式人生 > >GAP LOCK引起的死鎖

GAP LOCK引起的死鎖

先了解一下什麼是GAP LOCK

在INNODB中,record-level lock大致有三種:Record, Gap, and Next-KeyLocks。簡單的說,RECORDLOCK就是鎖住某一行記錄;而GAPLOCK會鎖住某一段範圍中的記錄;NEXT-KEYLOCK則是前兩者加起來的效果。

下面是MYSQL官方文件中相關內容的連結

有資料裡說MYSQL的GAP LOCK最初是為了避免Phantom (幻象讀)的問題,關於幻象讀這裡就不多做解釋了,可以參考如下連結

可是畢竟GAPLOCK導致了鎖定範圍的增大,在某些情況下可能會造成一些不符合預期的現象。下面是一個簡單的測試例子,先對GAP LOCK有個感性的認識

mysql> desc ts_column_log_test

    -> ;

+------------+-------------+------+-----+---------------------+----------------+

| Field      |Type        | Null | Key |Default            | Extra          |

+------------+-------------+------+-----+---------------------+----------------+

|id         |int(11)     | NO   | PRI |NULL               | auto_increment |

| col_id     |int(11)     | NO   | MUL |NULL               |               |

| start_time | timestamp   |NO   |     | 0000-00-00 00:00:00|               |

| end_time   |timestamp   | NO   |     | 0000-00-0000:00:00|               |

| data_time  | timestamp   |NO   |     | 0000-00-00 00:00:00|               |

| status     |varchar(30) | NO   |     |NULL               |               |

+------------+-------------+------+-----+---------------------+----------------+

6 rows in set (0.01 sec)

mysql> select * from ts_column_log_test; 

+----+--------+---------------------+---------------------+---------------------+---------+

| id | col_id |start_time          |end_time            |data_time           |status  |

+----+--------+---------------------+---------------------+---------------------+---------+

|  1 |      2| 2011-12-13 11:51:11 | 2011-12-13 11:51:11 | 2011-12-09 00:00:00 | running |

|  2 |     20 |2011-12-13 11:51:16 | 2011-12-13 11:51:16 | 2011-12-09 00:00:00 | running |

|  3 |    120 |2011-12-13 11:51:20 | 2011-12-13 11:51:20 | 2011-12-09 00:00:00 | running |

+----+--------+---------------------+---------------------+---------------------+---------+

3 rows in set (0.00 sec)

開啟兩個不同的會話,分別執行一些語句觀察一下結果:

session1

mysql> set autocommit=0;

mysql> delete from ts_column_log_testwhere col_id=10;

Query OK, 0 rows affected (0.00sec)        --此時[2,20)這個區間內的記錄都已經被GAP LOCK鎖住了,如果在其他事務中嘗試插入這些值,則會等待

session2

mysql> set autocommit=0;

mysql> INSERT INTO ts_column_log_test(col_id, start_time, end_time, data_time, status) VALUES (1, NULL, NULL,'20111209', 'running');  --成功

...

mysql> INSERT INTO ts_column_log_test(col_id, start_time, end_time, data_time, status) VALUES (2, NULL, NULL,'20111209', 'running');  --等待

...

mysql> INSERT INTO ts_column_log_test(col_id, start_time, end_time, data_time, status) VALUES (19, NULL, NULL,'20111209', 'running');  --等待

...

上面的實驗很簡單,大家可以自己測一下。這裡解釋一下會產生這種現象的原因:session1中的delete語句中指定條件where col_id=10,這時MYSQL會去掃描索引,但是這個時候delete語句獲取的不是一個RECORD LOCK,而是一個NEXT-KEY LOCK。以當前值(10)為例,會向左掃描至col_id=2這條記錄,向右掃描至col_id=20這條記錄,鎖定區間為前閉後開,即[2,20)。

下面是摘自官方手冊裡的一句話:

DELETE FROM ... WHERE ... sets an exclusivenext-key lock on every record the search encounters.

下面的連結裡面有INNODB中各種不同的語句可能持有哪些鎖的解釋

明白了GAPLOCK是怎麼回事,下面看下可能產生的問題吧

有時候我們會多個程序或執行緒並行的對同一張表進行操作,並且使用了事務,那麼就可能會有問題,舉個例子:

session1:

delete from ts_column_log_test wherecol_id=10;

INSERT INTO ts_column_log_test (col_id,start_time, end_time, data_time, status) VALUES (10, NULL, NULL, '20111209','running');

session2:

delete from ts_column_log_test wherecol_id=11;

INSERT INTO ts_column_log_test (col_id,start_time, end_time, data_time, status) VALUES (11, NULL, NULL, '20111209','running');

假設上面是你程式的兩個程序需要做的操作,在沒有併發的情況下,可能執行正常,因為每個事務在MYSQL中最終都是序列執行,中間並沒有其他事務同時進行;可併發高了以後,可能在MYSQL中實際執行的語句順序就會變成下面這個樣子:

tx_num  time            statement

111     2011-12-12 10:00:00 delete from ts_column_log_test wherecol_id=10;

222      2011-12-1210:00:00 delete from ts_column_log_test where col_id=11;

111     2011-12-12 10:00:00 INSERT INTO ts_column_log_test (col_id,start_time, end_time, data_time, status) VALUES (10, NULL, NULL, '20111209','running');

222      2011-12-1210:00:00 INSERT INTO ts_column_log_test (col_id, start_time, end_time,data_time, status) VALUES (11, NULL, NULL, '20111209', 'running');

這個時候,你可能就會得到錯誤提示ERROR 1213 (40001): Deadlock found when trying toget lock; try restarting transaction。

原因是前兩條語句都已經獲得了[2,20)這個區間內記錄的S鎖,然後兩個事務又分別對該區間段內的col_id=10這個位置請求X鎖,這時就發生死鎖,誰都請求不到X鎖,因為互相都持有S鎖。

解決方案有兩種

1、改變程式中資料庫操作的邏輯

2、取消GAP LOCK機制

Gap locking can be disabled explicitly.This occurs if you change the transaction isolation level to READ COMMITTED orenable the innodb_locks_unsafe_for_binlog system variable.