9、 InnoDB行锁
在 MySQL 中,InnoDB 行鎖通過給索引上的索引項加鎖來實現,如果沒有索引,InnoDB 將通過隱藏的聚簇索引來對記錄加鎖。
InnoDB 支持 3 種行鎖定方式:
- 行鎖(Record Lock):直接對索引項加鎖。
- 間隙鎖(Gap Lock):鎖加在索引項之間的間隙,也可以是第一條記錄前的“間隙”或最后一條記錄后的“間隙”。
- Next-Key Lock:行鎖與間隙鎖組合起來用就叫做 Next-Key Lock。 前兩種的組合,對記錄及其前面的間隙加鎖。
默認情況下,InnoDB 工作在可重復讀(默認隔離級別)下,并且以 Next-Key Lock 的方式對數據行進行加鎖,這樣可以有效防止幻讀的發生。
Next-Key Lock 是行鎖與間隙鎖的組合,這樣,當 InnoDB 掃描索引項的時候,會首先對選中的索引項加上行鎖(Record Lock),再對索引項兩邊的間隙(向左掃描掃到第一個比給定參數小的值, 向右掃描掃到第一個比給定參數大的值, 然后以此為界,構建一個區間)加上間隙鎖(Gap Lock)。如果一個間隙被事務 T1 加了鎖,其它事務不能在這個間隙插入記錄。
要禁止間隙鎖的話,可以把隔離級別降為讀已提交(READ COMMITTED),或者開啟參數 innodb_locks_unsafe_for_binlog。
注意:以上語句描述的情況,與 MySQL 所設置的事務隔離級別有較大的關系。
開啟一個事務時,InnoDB 存儲引擎會在更新的記錄上加行級鎖,此時其它事務不可以更新被鎖定的記錄。下面我們以示例1演示此過程。
例 1
下面的語句需要在兩個命令行窗口中執行。為了方便理解,我們分別稱之為 A 窗口和 B 窗口。
分別在 A 窗口和 B 窗口中查看事務隔離級別,A 窗口和 B 窗口的事務隔離級別需要保持一致。
A 窗口查看隔離級別的 SQL 語句和運行結果如下所示:
mysql> SHOW VARIABLES LIKE 'tx_isolation' \G *************************** 1. row *************************** Variable_name: tx_isolationValue: REPEATABLE-READ 1 row in set, 1 warning (0.03 sec)B 窗口查看隔離級別 SQL 語句和運行結果如下所示:
mysql> SHOW VARIABLES LIKE 'tx_isolation' \G *************************** 1. row *************************** Variable_name: tx_isolationValue: REPEATABLE-READ 1 row in set, 1 warning (0.03 sec)結果顯示,A窗口和 B窗口的事務隔離級別都為 REPEATABLE-READ。
在 A窗口中開啟一個事務,并修改 tb_student 表,SQL 語句和運行結果如下:
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE test.tb_student SET age ='30' WHERE id = 1;在 B窗口中也開啟一個事務,并修改 tb_student 表,SQL 語句和運行結果如下:
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE test.tb_student SET age ='30' WHERE id = 1;會發現 UPDATE 語句一直在執行。這時我們在 A 窗口中提交事務。
mysql> COMMIT;這時我們發現 B 窗口中的 UPDATE 語句執行成功。
mysql> UPDATE test.tb_student SET age ='30' WHERE id = 1; Query OK, 0 rows affected (1 min 2.78 sec) Rows matched: 1 Changed: 0 Warnings: 0查詢 tb_student 表中的數據,SQL 語句和運行結果如下:
mysql> SELECT * FROM test.tb_student; +----+------+------+------+------+ | id | name | age | sex | num | +----+------+------+------+------+ | 1 | 張三 | 30 | 男 | 4 | | 2 | 李四 | 12 | 男 | 4 | | 3 | 王五 | 13 | 女 | 4 | | 4 | 張四 | 13 | 女 | 4 | | 5 | 王四 | 15 | 男 | 4 | | 6 | 趙六 | 12 | 女 | 4 | +----+------+------+------+------+ 6 rows in set (0.00 sec)如以上實例所示,當有不同的事務同時更新同一條記錄時,另外一個事務需要等待另一個事務把鎖釋放,此時查看 MySQL 中 InnoDB 存儲引擎的狀態如下:
mysql> SHOW ENGINE innodb status \G ...... ------------ TRANSACTIONS ------------ Trx id counter 19556 Purge done for trx's n:o < 19554 undo n:o < 0 state: running but idle History list length 12 LIST OF TRANSACTIONS FOR EACH SESSION: ---TRANSACTION 283572223909376, not started 0 lock struct(s), heap size 1136, 0 row lock(s) ---TRANSACTION 19555, ACTIVE 54 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 14, OS thread handle 4568, query id 886 localhost ::1 root updating UPDATE test.tb_student SET age ='30' WHERE id = 1 ------- TRX HAS BEEN WAITING 54 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 197 page no 3 n bits 80 index PRIMARY of table `test`.`tb_student` trx id 19555 lock_mode X locks rec but not gap waiting Record lock, heap no 2 PHYSICAL RECORD: n_fields 7; compact format; info bits 0 0: len 4; hex 80000001; asc ;; 1: len 6; hex 000000004c62; asc Lb;;從上面運行結果可以看出,
SQL 語句 UPDATE test.tb_student SET age ='30' WHERE id = 1 在等待,
RECORD LOCKS space id 197 page no 3 n bits 80 index PRIMARY of tabletest.tb_studenttrx id 19555 lock_mode X locks rec but not gap 表示鎖住的資源,
locks rec but not gap 代表鎖住的是一個索引,不是一個范圍。
“MySQL thread id 14, OS thread handle 4568, query id 886 localhost ::1 root updating”表示第 2 個事務連接的 ID 為 14,當前狀態為正在更新,同時正在更新的記錄需要等待其它事務將鎖釋放。當超過事務等待鎖允許的最大時間,此時會提示“ERROR 1205(HY000):Lock wait timeout exceeded; try restarting transaction" 及當前事務執行失敗,則自動執行回滾操作。
MySQL 數據庫采用 InnoDB 模式,默認參數 innodb_lock_wait_timeout 設置鎖等待的時間是 50s,一旦數據庫鎖超過這個時間就會報錯。
可通過以下命令查看當前數據庫鎖等待的時間。
mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout'; +--------------------------+-------+ | Variable_name | Value | +--------------------------+-------+ | innodb_lock_wait_timeout | 120 | +--------------------------+-------+ 1 row in set, 1 warning (0.02 sec)下面演示了 InnoDB 間隙鎖的實現機制。
例 2
下面在保證 A 窗口和 B 窗口的前提下,將 tb_student 表中的 id 字段設為外鍵,并開啟一個事務,修改 tb_student 表中 id 為 1 的 age。SQL 語句和運行結果如下:
在 B 窗口中開啟一個事務,修改 tb_student 表中 id 為 2 的 age,SQL 語句和運行結果如下:
mysql> BEGIN; Query OK, 0 rows affected (0.00 sec) mysql> UPDATE test.tb_student SET age ='28'WHERE id=2;這時分別提交 A窗口和 B窗口的事務。
mysql> COMMIT; Query OK, 0 rows affected (0.01 sec)查詢 tb_student 表的數據,SQL 語句和運行結果如下:
mysql> SELECT * FROM test.tb_student; +----+------+------+------+------+ | id | name | age | sex | num | +----+------+------+------+------+ | 1 | 張三 | 31 | 男 | 4 | | 2 | 李四 | 28 | 男 | 4 | | 3 | 王五 | 13 | 女 | 4 | | 4 | 張四 | 13 | 女 | 4 | | 5 | 王四 | 15 | 男 | 4 | | 6 | 趙六 | 12 | 女 | 4 | +----+------+------+------+------+ 6 rows in set (0.00 sec)在上述示例中,由于 InnoDB 行級鎖為間隙鎖,只鎖定需要的記錄,因此 B窗口中的事務可以更新其它記錄,兩個事務之間互不影響。
總結
以上是生活随笔為你收集整理的9、 InnoDB行锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 8、MySQL表锁、行锁和页锁
- 下一篇: 11、MySQL字符集和校对规则详解