MySQL 版本:5.7
一、測試數據
測試數據庫:test;測試表:tt
CREATE TABLE `tt` ( `id` int(11) DEFAULT NULL, `name` varchar(100) DEFAULT NULL, KEY `name_idx` (`name`), KEY `id_idx` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
插入測試數據:
insert into tt value(1, "a”); insert into tt value(1, "b”); insert into tt value(2, “b");
二、數據庫服務設置
1、事務隔離級別設置
mysql> set global transaction_isolation = 'repeatable-read'; Query OK, 0 rows affected (0.00 sec) mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-UNCOMMITTED | +-------------------------+
2、鎖等待時間設置
mysql> set global innodb_lock_wait_timeout=5; Query OK, 0 rows affected (0.00 sec) mysql> select @@innodb_lock_wait_timeout; +----------------------------+ | @@innodb_lock_wait_timeout | +----------------------------+ | 5 | +----------------------------+
3、附註
三、讀未提交(READ-UNCOMMITTED)
開啓兩個連接 Session:
Session 1 | Session 2 |
開啓事務,更新 id 爲 2 的記錄 name 爲 “ss" ,保持事務未提交: Query OK, 0 rows affected (0.00 sec) mysql> select * from tt; +------+------+ | id | name | +------+------+ | 1 | a | | 1 | b | | 2 | b | +------+------+ 3 rows in set (0.00 sec) mysql> update tt set name = 'ss' where id = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 |
|
開啓事務,查詢 id 爲 2 的記錄 name 值: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | ss | +------+———+ 事務 2 可以查詢到事務 1 未提交的數據變更。對於事務 2 來說,這條數據是髒數據。 |
四、讀已提交(READ-COMMITTED)
解決 READ-UNCOMMITTED 隔離級別下產生的髒讀現象。
設置事務隔離級別:
mysql> set global transaction_isolation = 'read-committed'; Query OK, 0 rows affected (0.00 sec)
重新開啓測試 Session,查詢事務隔離級別:
mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-COMMITTED | +-------------------------+
Session 1 | Session 2 |
開啓事務,更新 id 爲 2 的記錄 name 爲 “ssr”:
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> update tt set name = 'ssr' where id = 2; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | ssr | +------+------+ 1 row in set (0.01 sec) |
|
查詢數據,無法查詢到 事務 1 未提交的數據:
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | b | +------+------+ 1 row in set (0.00 sec) |
|
提交事務:
mysql> commit ; Query OK, 0 rows affected (0.01 sec) |
|
查詢數據,得到的是事務 1 中已提交的數據變更:
mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | ssr | +------+------+ 1 row in set (0.00 sec) 對於事務 2 來說,在事務 1 提交前後,獲取到的數據是不一樣的,即不可重複讀問題。
|
五、可重複讀(REPEATABLE-READ)
解決 READ-COMMITTED 隔離級別下產生的不可重複讀現象。
Session 1中 設置事務隔離級別:
mysql> set global transaction_isolation = 'repeatable-read'; Query OK, 0 rows affected (0.01 sec)
重新開啓事務,查詢隔離級別:
mysql> select @@transaction_isolation; +-------------------------+ | @@transaction_isolation | +-------------------------+ | REPEATABLE-READ | +-------------------------+ 1 row in set (0.00 sec)
Session 1 | Session 2 |
|
Session 2 開啓事務,查詢數據: mysql> begin; Query OK, 0 rows affected (0.00 sec) Database changed mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | b | +------+------+ 1 row in set (0.00 sec) |
更新 id 爲 2 的記錄 name 爲 “ssrr”, 並提交事務: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> update tt set name = 'ssrr' where id = 2; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.01 sec) mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | ssrr | +------+------+ 1 row in set (0.00 sec) |
|
|
Session 2 重新查詢數據: mysql> select * from tt where id = 2; +------+------+ | id | name | +------+------+ | 2 | b | +------+------+ 當前數據未變。 但是問題是,事務 1 已經進行了數據變更,並且提交,事務 2 無法獲取所查記錄最新變更信息。 |
爲什麼事務 2 前後兩次相同查詢所得的數據是一樣的?
一致性讀(consistent read)查詢模式:基於【某一時刻】的【數據快照】提供讀查詢結果。無論查詢的數據是否被其它事務所改變。這個【某一時刻】在 repeatable-read 隔離級別下爲事務中第一次執行查詢操作的時間點,read-committed 隔離級別下,數據快照會在每一次執行一致性讀操作時進行重置。
幻讀
如何避免:加X鎖
Next-key lock:Record lock + Gap lock
六、關於 Next-key lock 加鎖
調整表 tt 索引及數據:
mysql> show create table tt; +-------+-------------------------------------------------------+ | Table | Create Table | +-------+-------------------------------------------------------+ | tt | CREATE TABLE `tt` ( `id` int(11) NOT NULL, `name` varchar(100) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_age` (`age`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 | +-------+-------------------------------------------------------+ 1 row in set (0.00 sec) mysql> select * from tt; +-----+------+------+ | id | name | age | +-----+------+------+ | 90 | aa | 10 | | 102 | bb | 15 | | 108 | cc | 20 | | 130 | dd | 25 | | 150 | ee | 30 | +-----+------+------+
1、等值條件
對於使用唯一性索引:加的鎖爲 Record lock
Session 1 | Session 2 |
開啓事務,查詢 id 爲 108 記錄加 X lock: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where id = 108 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ 1 row in set (0.01 sec) |
|
開啓事務,記錄前後緊鄰 gap 插入記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(106, 'bc', 16); Query OK, 1 row affected (0.00 sec) mysql> insert into tt value(110, 'cd', 22); Query OK, 1 row affected (0.00 sec) 記錄均可成功插入 |
對於使用非唯一性索引:加的鎖爲 Record lock + Gap lock 前後緊鄰 gap
:首先加鎖 (15, 20],因爲是非唯一索引,繼續向後查找到第一個不滿足條件的元素 25 加 gap lock (20, 25)
Session 1 | Session 2 |
開啓事務,查詢 age 爲 20 記錄加 X lock: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where age = 20 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ 1 row in set (0.00 sec) |
|
開啓事務,記錄緊鄰前後 gap 插入記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(106, 'bc', 18); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(110, 'cd', 22); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 操作均被 block。 緊鄰 gap 以外插入記錄: mysql> insert into tt value(100, 'ab', 12); Query OK, 1 row affected (0.00 sec) mysql> insert into tt value(140, 'de', 27); Query OK, 1 row affected (0.00 sec) 記錄均可成功插入 |
對於不使用索引的:加鎖爲全部記錄及gap
Session1
|
Session2 |
開啓事務,查詢 name 爲 ‘cc’ 記錄加 X lock: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where name = 'cc' for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ |
|
開啓事務,各個間隙嘗試插入記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(80, 'pa', 5); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(95, 'ab', 13); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(105, 'bc', 18); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(120, 'cd', 23); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(140, 'de', 28); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(160, 'en', 35); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 操作均被 block。 更新記錄: mysql> update tt set age = 21 where name = 'cc'; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> update tt set age = 16 where name = 'bb'; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 操作均被 block。 |
2、範圍條件
使用唯一索引:
Session 1 | Session 2 |
開啓事務,查詢 id 爲 108 的記錄用以更新 mysql> select * from tt where id >= 108 and id < 109 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ 1 row in set (0.01 sec) |
|
開啓事務,間隙插入記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(120, 'cd', 23); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> update tt set age = 26 where id = 130; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 操作被 block。 加鎖說明: >= 108 條件:主鍵索引加鎖爲 Record lock 記錄 108 < 109 條件:因爲不存在 109 記錄,所以繼續向右遍歷至 130 不滿足,加鎖 (108, 130] |
|
重新開啓事務,右側條件改爲開區間 130: mysql> rollback; Query OK, 0 rows affected (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where id >= 108 and id < 130 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ 1 row in set (0.01 sec) |
|
開啓事務,操作 130 後間隙插入記錄及更新 id 爲 130 記錄: mysql> insert into tt value(135, 'ce', 32); Query OK, 1 row affected (0.01 sec) mysql> rollback; Query OK, 0 rows affected (0.01 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> update tt set age = 26 where id = 130; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 插入操作成功,更新操作被 block。 加鎖說明: < 130 條件:存在 130 記錄,加鎖 (108, 130] |
|
重新開啓事務,右側條件改爲閉區間 130 mysql> rollback; Query OK, 0 rows affected (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where id >= 108 and id <= 130 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | | 130 | dd | 25 | +-----+------+------+ |
|
開啓事務,操作 130 後間隙插入記錄及更新 id 爲 150 記錄: mysql> rollback; Query OK, 0 rows affected (0.01 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(140, 'de', 28); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> update tt set age = 35 where id = 150; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 插入操作成功,更新操作被 block。 加鎖說明: <= 130 條件:存在 130 記錄,加鎖 (108, 130],繼續向右查詢到不滿足條件記錄 150,加鎖 (130, 150] |
2、使用非唯一索引
Session 1 | Session 2 |
開啓事務,查詢 age 範圍記錄用以更新: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where age >= 20 and age < 21 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ |
|
開啓事務,間隙插入記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(105, 'bc', 18); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(120, 'cd', 23); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(140, 'de', 28); Query OK, 1 row affected (0.00 sec) mysql> update tt set name = 'test' where age = 25; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 加鎖說明: >= 20 條件:非唯一索引,加鎖爲 (15, 20] < 21 條件:因爲不存在 21 記錄,所以繼續向右遍歷至 25 不滿足,加鎖 (20, 25] |
|
重新開啓事務,右側條件改爲開區間 25 mysql> rollback; Query OK, 0 rows affected (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where age >= 20 and age < 25 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ |
|
開啓事務,操作 25 後間隙插入記錄及更新 id 爲 25 記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(140, 'de', 28); Query OK, 1 row affected (0.00 sec) mysql> update tt set name = 'test' where age = 25; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 插入操作成功,更新操作被 block。 加鎖說明: < 25 條件:存在 25 記錄,加鎖 (20, 25] |
|
重新開啓事務,右側條件改爲閉區間 25 mysql> rollback; Query OK, 0 rows affected (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from tt where age >= 20 and age <= 25 for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | | 130 | dd | 25 | +-----+------+------+ |
|
開啓事務,操作 25 後間隙插入記錄及更新 age 爲 30 記錄: mysql> rollback; Query OK, 0 rows affected (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(140, 'de', 28); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> update tt set name = 'test' where age = 30; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 插入操作成功,更新操作被 block。 加鎖說明: <= 25 條件:存在 25 記錄,加鎖 (20, 25],繼續向右查詢到不滿足條件記錄 30,加鎖 (25, 30] |
3、 不使用索引
Session 1 | Session 2 |
開啓事務,查詢 name 範圍記錄用以更新: mysql> select * from tt where name >= 'cc' and name < 'dd' for update; +-----+------+------+ | id | name | age | +-----+------+------+ | 108 | cc | 20 | +-----+------+------+ |
|
開啓事務,間隙插入記錄: mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into tt value(85, 'pa', 5); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(95, 'ab', 13); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(105, 'bc', 18); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(120, 'cd', 23); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(140, 'de', 28); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into tt value(160, 'et', 50); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction 加鎖說明: 間隙全部加鎖 |