衆所周知,MySQL 的 InnoDB 存儲引擎支持事務,默認是行鎖。得益於這些特性,數據庫支持高併發。若是 InnoDB 更新數據使用的不是行鎖,而是表鎖呢?是的,InnoDB 其實很容易就升級爲表鎖,屆時併發性將大打折扣了。
通過我操做驗證,得出行鎖升級爲表鎖的緣由之一是: SQL 語句中未使用到索引,或者說使用的索引未被數據庫承認(至關於沒有使用索引)。javascript
我相信,MySQL InnoDB 存儲引擎引起表鎖的緣由確定不止一個因素,針對其解決方法也不是隻有一種。前端
【掘金】上另外一位做者【Blink-前端】,提出行鎖升級爲表鎖與 事務的隔離級別 有關,並給出了事例。固然,我贊成這個說法,由於事務的隔離性是靠加鎖來實現的,而加鎖勢必會影響併發。本篇只針對 索引影響併發 做出說明,並特別但願有朋友能提出質疑並給出獨特看法,萬分感謝。java
既然談及索引是影響併發的決定因素之一,那咱們就來了解一下索引這位主角。mysql
經常使用的索引有三類:主鍵、惟一索引、普通索引。主鍵 不禁分說,自帶最高效的索引屬性;惟一索引 指的是該屬性值重複率爲0,通常可做爲業務主鍵,例如學號;普通索引 與前者不一樣的是,屬性值的重複率大於0,不能做爲惟一指定條件,例如學生姓名。接下來我要說明是 「普通索引對併發的影響」。sql
爲何我會想到 「普通索引對併發有影響」?這源自【掘金】微信羣拋出的一個問題:數據庫
mysql 5.6 在 update 和 delete 的時候,where 條件若是不存在索引字段,那麼這個事務是否會致使表鎖?微信
有人回答:session
只有主鍵和惟一索引纔是行鎖,普通索引是表鎖。併發
我針對 「普通索引是表鎖」 進行了驗證,結果發現普通索引並不必定會引起表鎖,在普通索引中,是否引起表鎖取決於普通索引的高效程度。高併發
上文說起的「高效」是相對主鍵和惟一索引而言,也許「高效」並非一個很好的解釋,明白在通常i狀況下,「普通索引」效率低於其餘二者便可。
爲了突出效果,我將「普通索引」創建在一個「值重複率」高的屬性下。以相對極端的方式,擴大對結果的影響。
我會建立一張「分數等級表」,屬性有「id」、「score(分數)」、「level(等級)」,模擬一個半自動的業務——「分數」已被自動導入,而「等級」須要手工更新。
操做步驟以下:
取消 事務自動提交:
mysql> set autocommit = off;
Query OK, 0 rows affected (0.02 sec)
mysql> show variables like "autocommit";
+--------------------------+-------+
| Variable_name | Value |
+--------------------------+-------+
| autocommit | OFF |
+--------------------------+-------+
1 rows in set (0.01 sec)複製代碼
建表、建立索引、插入數據:
DROP TABLE IF EXISTS `test1`;
CREATE TABLE `test1` (
`ID` int(5) NOT NULL AUTO_INCREMENT ,
`SCORE` int(3) NOT NULL ,
`LEVEL` int(2) NULL DEFAULT NULL ,
PRIMARY KEY (`ID`)
)ENGINE=InnoDB DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci;
ALTER TABLE `test2` ADD INDEX index_name ( `SCORE` );
INSERT INTO `test1`(`SCORE`) VALUE (100);
……
INSERT INTO `test1`(`SCORE`) VALUE (0);
……複製代碼
"SCORE" 屬性的「值重複率」奇高,達到了 50%,劍走偏鋒:
mysql> select * from test1;
+----+-------+-------+
| ID | SCORE | LEVEL |
+----+-------+-------+
| 1 | 100 | NULL |
| 2 | 0 | NULL |
| 5 | 100 | NULL |
| 6 | 100 | NULL |
| 7 | 100 | NULL |
| 8 | 100 | NULL |
| 9 | 100 | NULL |
| 10 | 100 | NULL |
| 11 | 100 | NULL |
| 12 | 100 | NULL |
| 13 | 100 | NULL |
| 14 | 0 | NULL |
| 15 | 0 | NULL |
| 16 | 0 | NULL |
| 17 | 0 | NULL |
| 18 | 0 | NULL |
| 19 | 0 | NULL |
| 20 | 0 | NULL |
| 21 | 0 | NULL |
| 22 | 0 | NULL |
| 23 | 0 | NULL |
| 24 | 100 | NULL |
| 25 | 0 | NULL |
| 26 | 100 | NULL |
| 27 | 0 | NULL |
+----+-------+-------+
25 rows in set複製代碼
開啓兩個事務(一個窗口對應一個事務),並選定數據:
-- SESSION_1,選定 SCORE = 100 的數據
mysql> BEGIN;
SELECT t.* FROM `test1` t WHERE t.`SCORE` = 100 FOR UPDATE;
Query OK, 0 rows affected
+----+-------+-------+
| ID | SCORE | LEVEL |
+----+-------+-------+
| 1 | 100 | NULL |
| 5 | 100 | NULL |
| 6 | 100 | NULL |
| 7 | 100 | NULL |
| 8 | 100 | NULL |
| 9 | 100 | NULL |
| 10 | 100 | NULL |
| 11 | 100 | NULL |
| 12 | 100 | NULL |
| 13 | 100 | NULL |
| 24 | 100 | NULL |
| 26 | 100 | NULL |
+----+-------+-------+
12 rows in set複製代碼
再打開一個窗口:
-- SESSION_2,選定 SCORE = 0 的數據
mysql> BEGIN;
SELECT t.* FROM `test1` t WHERE t.`SCORE` = 0 FOR UPDATE;
Query OK, 0 rows affected
+----+-------+-------+
| ID | SCORE | LEVEL |
+----+-------+-------+
| 2 | 0 | NULL |
| 14 | 0 | NULL |
| 15 | 0 | NULL |
| 16 | 0 | NULL |
| 17 | 0 | NULL |
| 18 | 0 | NULL |
| 19 | 0 | NULL |
| 20 | 0 | NULL |
| 21 | 0 | NULL |
| 22 | 0 | NULL |
| 23 | 0 | NULL |
| 25 | 0 | NULL |
| 27 | 0 | NULL |
+----+-------+-------+
13 rows in set複製代碼
session_1 窗口,更新「LEVEL」失敗:
mysql> UPDATE `test1` SET `LEVEL` = 1 WHERE `SCORE` = 100;
1205 - Lock wait timeout exceeded; try restarting transaction複製代碼
在以前的操做中,session_1 選擇了 `SCORE` = 100 的數據,session_2 選擇了 `SCORE` = 0 的數據,看似兩個事務井水不犯河水,可是在 session_1 事務中更新本身鎖定的數據失敗,只能說明在此時引起了表鎖。彆着急,剛剛走向了一個極端——索引屬性值重複性奇高,接下來走向另外一個極端。
仍是同一張表,將數據刪除只剩下兩條,「SCORE」 的 「值重複率」 爲 0:
mysql> delete from test1 where id > 2;
Query OK, 23 rows affected
mysql> select * from test1;
+----+-------+-------+
| ID | SCORE | LEVEL |
+----+-------+-------+
| 1 | 100 | NULL |
| 2 | 0 | NULL |
+----+-------+-------+
2 rows in set複製代碼
關閉兩個事務操做窗口,從新開啓 session_1 和 session_2,並選擇各自須要的數據:
-- SESSION_1,選定 SCORE = 100 的數據
mysql> BEGIN;
SELECT t.* FROM `test1` t WHERE t.`SCORE` = 100 FOR UPDATE;
Query OK, 0 rows affected
+----+-------+-------+
| ID | SCORE | LEVEL |
+----+-------+-------+
| 1 | 100 | NULL |
+----+-------+-------+
1 row in set
-- -----------------新窗口----------------- --
-- SESSION_2,選定 SCORE = 0 的數據
mysql> BEGIN;
SELECT t.* FROM `test1` t WHERE t.`SCORE` = 0 FOR UPDATE;
Query OK, 0 rows affected
+----+-------+-------+
| ID | SCORE | LEVEL |
+----+-------+-------+
| 2 | 0 | NULL |
+----+-------+-------+
1 row in set複製代碼
session_1 更新數據成功:
mysql> UPDATE `test1` SET `LEVEL` = 1 WHERE `SCORE` = 100;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0複製代碼
相同的表結構,相同的操做,兩個不一樣的結果讓人出乎意料。第一個結果讓人以爲「普通索引」引起表鎖,第二個結果推翻了前者,兩個操做中,惟一不一樣的是索引屬性的「值重複率」。根據 單一變量 證實法,能夠得出結論:當「值重複率」低時,甚至接近主鍵或者惟一索引的效果,「普通索引」依然是行鎖;當「值重複率」高時,MySQL 不會把這個「普通索引」當作索引,即形成了一個沒有索引的 SQL,此時引起表鎖。
索引不是越多越好,索引存在一個和這個表相關的文件裏,佔用硬盤空間,寧缺勿濫,每一個表都有主鍵(id),操做能使用主鍵儘可能使用主鍵。
同 JVM 自動優化 java 代碼同樣,MySQL 也具備自動優化 SQL 的功能。低效的索引將被忽略,這也就倒逼開發者使用正確且高效的索引。
轉載請註明出處:zhoupq.com/MySQL-%E9%8…