InnoDB行鎖實現方式

zlingyi發表於2017-03-22
nnoDB行鎖是透過給索引上的索引項加鎖來實現的,這一點MySQL與Oracle不同,後者是透過在資料塊中對相應資料行加鎖來實現的。InnoDB這種行鎖實現特點意味著:只有透過索引條件檢索資料,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!

在實際應用中,要特別注意InnoDB行鎖的這一特性,不然的話,可能導致大量的鎖衝突,從而影響併發效能。下面透過一些實際例子來加以說明。

(1)在不透過索引條件查詢的時候,InnoDB確實使用的是表鎖,而不是行鎖。

在如表20-9所示的例子中,開始tab_no_index表沒有索引:

  1. mysql> create table tab_no_index(id int,name varchar(10)) engine=innodb;
  2. Query OK, 0 rows affected (0.15 sec)
  3. mysql> insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');
  4. Query OK, 4 rows affected (0.00 sec)
  5. Records: 4  Duplicates: 0  Warnings: 0
複製程式碼

表20-9               InnoDB儲存引擎的表在不使用索引時使用表鎖例子            
session_1

session_2

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tab_no_index where id = 1 ;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
+------+------+
1 row in set (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tab_no_index where id = 2 ;
+------+------+
| id   | name |
+------+------+
| 2    | 2    |
+------+------+
1 row in set (0.00 sec)

mysql> select * from tab_no_index where id = 1 for update;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
+------+------+
1 row in set (0.00 sec)



mysql> select * from tab_no_index where id = 2 for update;
等待


在如表20-9所示的例子中,看起來session_1只給一行加了排他鎖,但session_2在請求其他行的排他鎖時,卻出現了鎖等待!原因就是在沒有索引的情況下,InnoDB只能使用表鎖。當我們給其增加一個索引後,InnoDB就只鎖定了符合條件的行,如表20-10所示。
建立tab_with_index表,id欄位有普通索引:

  1. mysql> create table tab_with_index(id int,name varchar(10)) engine=innodb;
  2. Query OK, 0 rows affected (0.15 sec)
  3. mysql> alter table tab_with_index add index id(id);
  4. Query OK, 4 rows affected (0.24 sec)
  5. Records: 4  Duplicates: 0  Warnings: 0
複製程式碼

表20-10                                InnoDB儲存引擎的表在使用索引時使用行鎖例子
session_1

session_2

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tab_with_index where id = 1 ;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
+------+------+
1 row in set (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tab_with_index where id = 2 ;
+------+------+
| id   | name |
+------+------+
| 2    | 2    |
+------+------+
1 row in set (0.00 sec)

mysql> select * from tab_with_index where id = 1 for update;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
+------+------+
1 row in set (0.00 sec)



mysql> select * from tab_with_index where id = 2 for update;
+------+------+
| id   | name |
+------+------+
| 2    | 2    |
+------+------+
1 row in set (0.00 sec)


(2)由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果是使用相同的索引鍵,是會出現鎖衝突的。應用設計的時候要注意這一點。

在如表20-11所示的例子中,表tab_with_index的id欄位有索引,name欄位沒有索引:

  1. mysql> alter table tab_with_index drop index name;
  2. Query OK, 4 rows affected (0.22 sec)
  3. Records: 4  Duplicates: 0  Warnings: 0
  4. mysql> insert into tab_with_index  values(1,'4');
  5. Query OK, 1 row affected (0.00 sec)
  6. mysql> select * from tab_with_index where id = 1;
  7. +------+------+
  8. | id   | name |
  9. +------+------+
  10. | 1    | 1    |
  11. | 1    | 4    |
  12. +------+------+
  13. 2 rows in set (0.00 sec)
複製程式碼

表20-11                InnoDB儲存引擎使用相同索引鍵的阻塞例子      
session_1

session_2

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 and name = '1' for update;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
+------+------+
1 row in set (0.00 sec)



雖然session_2訪問的是和session_1不同的記錄,但是因為使用了相同的索引,所以需要等待鎖:
mysql> select * from tab_with_index where id = 1 and name = '4' for update;
等待


(3)當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,另外,不論是使用主鍵索引、唯一索引或普通索引,InnoDB都會使用行鎖來對資料加鎖。
在如表20-12所示的例子中,表tab_with_index的id欄位有主鍵索引,name欄位有普通索引:

  1. mysql> alter table tab_with_index add index name(name);
  2. Query OK, 5 rows affected (0.23 sec)
  3. Records: 5  Duplicates: 0  Warnings: 0
複製程式碼

表20-12                                  InnoDB儲存引擎的表使用不同索引的阻塞例子
session_1

session_2

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tab_with_index where id = 1 for update;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
| 1    | 4    |
+------+------+
2 rows in set (0.00 sec)



Session_2使用name的索引訪問記錄,因為記錄沒有被索引,所以可以獲得鎖:
mysql> select * from tab_with_index where name = '2' for update;
+------+------+
| id   | name |
+------+------+
| 2    | 2    |
+------+------+
1 row in set (0.00 sec)


由於訪問的記錄已經被session_1鎖定,所以等待獲得鎖。:
mysql> select * from tab_with_index where name = '4' for update;


(4)即便在條件中使用了索引欄位,但是否使用索引來檢索資料是由MySQL透過判斷不同執行計劃的代價來決定的,如果MySQL認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況下InnoDB將使用表鎖,而不是行鎖。因此,在分析鎖衝突時,別忘了檢查SQL的執行計劃,以確認是否真正使用了索引。關於MySQL在什麼情況下不使用索引的詳細討論,參見本章“索引問題”一節的介紹。
在下面的例子中,檢索值的資料型別與索引欄位不同,雖然MySQL能夠進行資料型別轉換,但卻不會使用索引,從而導致InnoDB使用表鎖。透過用explain檢查兩條SQL的執行計劃,我們可以清楚地看到了這一點。
例子中tab_with_index表的name欄位有索引,但是name欄位是varchar型別的,如果where條件中不是和varchar型別進行比較,則會對name進行型別轉換,而執行的全表掃描。

  1. mysql> alter table tab_no_index add index name(name);
  2. Query OK, 4 rows affected (8.06 sec)
  3. Records: 4  Duplicates: 0  Warnings: 0
  4. mysql> explain select * from tab_with_index where name = 1 \G
  5. *************************** 1. row ***************************
  6.            id: 1
  7.   select_type: SIMPLE
  8.         table: tab_with_index
  9.          type: ALL
  10. possible_keys: name
  11.           key: NULL
  12.       key_len: NULL
  13.           ref: NULL
  14.          rows: 4
  15.         Extra: Using where
  16. 1 row in set (0.00 sec)
  17. mysql> explain select * from tab_with_index where name = '1' \G
  18. *************************** 1. row ***************************
  19.            id: 1
  20.   select_type: SIMPLE
  21.         table: tab_with_index
  22.          type: ref
  23. possible_keys: name
  24.           key: name
  25.       key_len: 23
  26.           ref: const
  27.          rows: 1
  28.         Extra: Using where
  29. 1 row in set (0.00 sec)

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28686045/viewspace-2135789/,如需轉載,請註明出處,否則將追究法律責任。

相關文章