記錄一個MySql 分區表+Gap鎖引發插入超時的案例

最近有同事在項目上遇到一個場景,定時任務在往MySql插入一條數據超時了,而排查其餘SQL,沒有鎖表的動做。排查到最後,發現是分區表致使id不惟一,加上Gap鎖致使的。下面簡單分析一下。html

1.場景重現spa

1.1 沒有分區的場景code

先建一個沒有分區的表htm

1 CREATE TABLE student (
2     `id` INT NOT NULL PRIMARY KEY,
3     `name` VARCHAR (128) DEFAULT NULL,
4     `country` VARCHAR(64) 
5 ) ENGINE = INNODB DEFAULT charset = utf8;

插入一些數據blog

1 INSERT INTO STUDENT (`id`, `name`, `country`)
2 VALUES
3     (1, 'name1', 'CHINA'),
4     (3, 'name3', 'JAPAN'),
5     (5, 'name5', 'USA'),
6     (7, 'name7', 'JAPAN'),
7     (9, 'name9', 'CHINA');

此時開啓一個事務(稱爲事務1),把id=5的數據改成changed索引

事務1未提交,同時在另一個窗口,再開一個事務(稱爲事務2),插入id爲4的數據;事務

1 insert into student(`id`,`name`,`country`) values (4,'name4','USA');

 

  成功插入,兩個事務分別提交,能夠看到數據已經寫入。get

這很好理解,兩條語句操做的id不同,不會互相影響。但是加了分區,就不同了。it

1.2 分區的場景io

1 CREATE TABLE student (
2     `id` INT NOT NULL,
3     `name` VARCHAR (128) DEFAULT NULL,
4   `country` VARCHAR(64) NOT NULL,
5     PRIMARY KEY(id,country)
6 ) ENGINE = INNODB DEFAULT charset = utf8 partition BY KEY (country) PARTITIONS 4;

 

注意,主鍵已經不僅是id了,而是id+country。若是不把country加入到主鍵中,會報錯。

[Err] 1503 - A PRIMARY KEY must include all columns in the table's partitioning function

而這致使了經過id並不能惟一確認一條數據(雖然從程序的邏輯上是惟一的),進而致使更新時會鎖住不止一行,影響了其餘事務的操做。

 

插入數據,此時country值是同樣的(後面解釋)

1 INSERT INTO STUDENT (`id`, `name`, `country`)
2 VALUES
3     (1, 'name1', 'CHINA'),
4     (3, 'name3', 'CHINA'),
5     (5, 'name5', 'CHINA'),
6     (7, 'name7', 'CHINA'),
7     (9, 'name9', 'CHINA');

和上面同樣,開一個事務1,update id=5的數據,暫不提交。

在事務2,前後插入id=8和id=4的數據,能夠看到,第一條順利插入,第二條被阻塞,直到事務超時。

 

 

2. 緣由簡析

參考美團技術團隊關於MySql鎖的分析:https://tech.meituan.com/2014/08/20/innodb-lock.html

簡單來講,就是MySql的默認事務級別是RR,在這個級別是解決了幻讀問題的。不可重複讀和幻讀的關注點不同,前者是update/delete,後者是insert.

爲了防止幻讀,MySql用Gap鎖把id=5的數據先後都鎖住了,即(3,5] 和(5,7],因此插入id=4或6的數據會被阻塞,而插入8不會。

那麼爲何明明有ID,還要分區呢?這是由於有時候數據量大,利用分區能夠訪問快一點,例如以記錄的create time做爲分區依據。但這樣就要求分區字段做爲主鍵的一部分,破壞了原主鍵的惟一性。

 

上面的例子,是基於country=CHINA的分析,由於經過實驗,這個鎖對於一樣分區的才生效。若是把其餘數據的country,改成USA或JAPAN,結果又不同了。

好比把7改成JAPAN, 這時(5,'CHINA') 和 (7,'JAPAN') 並不在同個分區,那麼Gap鎖鎖住的是(3,5]和(5,9],插入id=8, country=CHINA一樣阻塞(固然插入id=8, country=JAPAN不會阻塞,不在同個分區!)。

因此實際上是 分區->ID+分區字段才能組成主鍵->誤覺得ID是主鍵,但實際並不惟一,致使了Gap鎖->多鎖了幾行

 

還有其餘的場景,參考這個例子:https://www.zhihu.com/question/51390849/answer/294352412

事務2若是是update,而且改變了索引字段的值,也就改變了索引的位置,那麼更新後的數據可能落入gap區間,形成阻塞。

若是不改變索引字段的值,不會有這樣的問題。

相關文章
相關標籤/搜索