RR 隔離演示: mysql> show create table rr_100\G; CREATE TABLE `rr_100` ( `id` bigint(20) NOT NULL default '0', `value` varchar(32) default NULL, PRIMARY KEY (`id`)); mysql> select @@global.tx_isolation, @@tx_isolation; +-----------------------+-----------------+ | @@global.tx_isolation | @@tx_isolation | +-----------------------+-----------------+ | REPEATABLE-READ | REPEATABLE-READ | +-----------------------+-----------------+ 1 row in set (0.00 sec) mysql> show create table rr_100\G; *************************** 1. row *************************** Table: rr_100 Create Table: CREATE TABLE `rr_100` ( `id` bigint(20) NOT NULL DEFAULT '0', `value` varchar(32) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) ERROR: No query specified 必須開啓事務,才能實現重複讀,若是不開啓事務: Session A: Session B: mysql> select * from rr_100; Empty set (0.00 sec) mysql> insert into rr_100 values(1,'a'); Query OK, 1 row affected (0.01 sec) mysql> select * from rr_100; +----+-------+ | id | value | +----+-------+ | 1 | a | +----+-------+ 1 row in set (0.00 sec) 此時 沒有開啓事務,不會出現重複讀,開啓事務後: Session A: Session B: mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from rr_100; Empty set (0.00 sec) mysql> insert into rr_100 values(1,'a'); Query OK, 1 row affected (0.00 sec) Session A: mysql> select * from rr_100; Empty set (0.00 sec) mysql> insert into rr_100 values(1,'a'); ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' 如此就出現了幻讀,覺得表裏沒有數據,其實數據已經存在了,傻乎乎的提交後,才發現數據衝突了。 mysql> select * from rr_100; Empty set (0.00 sec) mysql> delete from rr_100 ; Query OK, 1 row affected (0.00 sec) 查看沒有記錄,刪除確有數據 那麼,InnoDB指出的能夠避免幻讀是怎麼回事呢? By default, InnoDB operates in REPEATABLE READ transaction isolation level and with the innodb_locks_unsafe_for_binlog system variable disabled. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows (see Section 13.6.8.5, 「Avoiding the Phantom Problem Using Next-Key Locking」). 默認,InnoDB 操做在 REPEATABLE READ 事務隔離級別, innodb_locks_unsafe_for_binlog 系統變量是disabled的 在這種狀況下,InnoDB 使用 next-key locks對於搜索和索引掃描,提供了防止幻影行。 爲了防止幻讀,InnoDB 使用一個算法稱爲 next-key locking 組合 index-row locking 和gap locking. 爲了防止幻讀,InnoDB 使用一算法稱爲next-key locking 結合 index-row locking 和區間鎖 你可使用 next-key locking來實現 一個惟一檢查在你的應用裏。 你可使用 next-key locking 來實現一個惟一的檢查在你的應用裏: 若是你讀取你的數據在share mode和不像看到重複的對於一個記錄你想要插入, 而後你能夠安全的插入你的記錄,ZHi到 next-key lock 設置成功在你讀取先前的任何一個 由於,next-key locking 讓你"lock" 不存在的記錄在你的表裏 For locking reads (SELECT with FOR UPDATE or LOCK IN SHARE MODE),UPDATE, and DELETE statements, locking depends on whether the statement uses a unique index with a unique search condition, or a range-type search condition. For a unique index with a unique search condition, InnoDB locks only the index record found, not the gap before it. For other search conditions, InnoDB locks the index range scanned, using gap locks or next-key (gap plus index-record) locks to block insertions by other sessions into the gaps covered by the range. 對於鎖定讀((SELECT with FOR UPDATE or LOCK IN SHARE MODE),UPDATE,和DELETE 語句 鎖依賴因而否語句使用一個惟一索引 惟一搜索條件, 或者一個範圍類型的搜索條件。 對於一個惟一的索引進行惟一搜索條件,InnoDB 只鎖定index record記錄,不是它以前的區間。 對於其餘的搜索條件,InnoDB 鎖定index rang scanned,使用gap locks 或者next-key(gap 加上index-record) 鎖來堵塞其餘會話插入到範圍覆蓋的區間 一致性讀和提交讀,先看實驗,實驗四: Sesssion 1: Session 2: mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from rr_100; +----+-------+ | id | value | +----+-------+ | 1 | a | +----+-------+ 1 row in set (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from rr_100; +----+-------+ | id | value | +----+-------+ | 1 | a | +----+-------+ 1 row in set (0.00 sec) mysql> insert into rr_100 values(2,'b'); Query OK, 1 row affected (0.00 sec) mysql> commit; Query OK, 0 rows affected (0.01 sec) mysql> select * from rr_100; +----+-------+ | id | value | +----+-------+ | 1 | a | | 2 | b | +----+-------+ 2 rows in set (0.00 sec) mysql> select * from rr_100 ; +----+-------+ | id | value | +----+-------+ | 1 | a | +----+-------+ 1 row in set (0.00 sec) mysql> select * from rr_100 for update; +----+-------+ | id | value | +----+-------+ | 1 | a | | 2 | b | +----+-------+ mysql> select * from rr_100 LOCK IN SHARE MODE; +----+-------+ | id | value | +----+-------+ | 1 | a | | 2 | b | +----+-------+ 2 rows in set (0.00 sec) 2 rows in set (0.00 sec) 若是使用普通的讀,會獲得一致性的結果,若是使用了加鎖的讀,就會讀到「最新的」「提交」讀的結果。 自己,可重複讀和提交讀是矛盾的。在同一個事務裏,若是保證了可重複讀,就會看不到其餘事務的提交,違背了提交讀;若是保證了提交讀,就會致使先後兩次讀到的結果不一致,違背了可重複讀。 If you want to see the 「freshest」 state of the database, you should use either the READ COMMITTED isolation level or a locking read: SELECT * FROM t_bitfly LOCK IN SHARE MODE; 若是你須要看到最新鮮狀態的數據,你可使用 READ COMMITTED isolation level 或者一個鎖定讀: mysql> select * from rr_100 LOCK IN SHARE MODE; +----+-------+ | id | value | +----+-------+ | 1 | a | | 2 | b | +----+-------+ 結論:MySQL InnoDB的可重複讀並不保證避免幻讀,須要應用使用加鎖讀來保證。而這個加鎖度使用到的機制就是next-key locks。 next-key locks(gap 加上index-record)測試: CREATE TABLE `Sms_rr` ( `sn` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增編號', `phoneNo` int(16) NOT NULL , `message` varchar(250) NOT NULL , `channelType` int(11) DEFAULT NULL COMMENT '通道識別', `status` tinyint(4) NOT NULL, PRIMARY KEY (`sn`) ) ENGINE=InnoDB AUTO_INCREMENT=1 1 row in set (0.00 sec) mysql> show create table Sms_rr\G; *************************** 1. row *************************** Table: Sms_rr Create Table: CREATE TABLE `Sms_rr` ( `sn` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增編號', `phoneNo` int(16) NOT NULL, `message` varchar(250) NOT NULL, `channelType` int(11) DEFAULT NULL COMMENT '通道識別', `status` tinyint(4) NOT NULL, PRIMARY KEY (`sn`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql> create index Sms_rr_idx1 on Sms_rr(phoneNo); Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> show create table Sms_rr\G; *************************** 1. row *************************** Table: Sms_rr Create Table: CREATE TABLE `Sms_rr` ( `sn` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增編號', `phoneNo` int(16) NOT NULL, `message` varchar(250) NOT NULL, `channelType` int(11) DEFAULT NULL COMMENT '通道識別', `status` tinyint(4) NOT NULL, PRIMARY KEY (`sn`), KEY `Sms_rr_idx1` (`phoneNo`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8 1 row in set (0.00 sec) mysql> select * from Sms_rr; +----+---------+---------+-------------+--------+ | sn | phoneNo | message | channelType | status | +----+---------+---------+-------------+--------+ | 1 | 1 | xxxxxxx | 2 | 1 | | 2 | 2 | xxxxxxx | 2 | 1 | | 3 | 3 | xxxxxxx | 2 | 1 | | 4 | 4 | xxxxxxx | 2 | 1 | | 5 | 5 | xxxxxxx | 2 | 1 | | 6 | 6 | xxxxxxx | 2 | 1 | | 7 | 7 | xxxxxxx | 2 | 1 | | 8 | 8 | xxxxxxx | 2 | 1 | | 9 | 9 | xxxxxxx | 2 | 1 | | 10 | 10 | xxxxxxx | 1 | 1 | | 11 | 11 | xxxxxxx | 2 | 1 | | 12 | 12 | xxxxxxx | 2 | 1 | | 13 | 13 | xxxxxxx | 2 | 1 | | 14 | 14 | xxxxxxx | 2 | 1 | | 15 | 15 | xxxxxxx | 2 | 1 | | 16 | 16 | xxxxxxx | 2 | 1 | | 17 | 17 | xxxxxxx | 2 | 1 | | 18 | 18 | xxxxxxx | 1 | 1 | | 19 | 19 | xxxxxxx | 1 | 1 | | 20 | 20 | xxxxxxx | 2 | 1 | | 23 | 23 | ttt | 2 | 3 | | 32 | 32 | xxxxxxx | 2 | 1 | | 33 | 33 | xxxxxxx | 2 | 1 | | 34 | 34 | xxxxxxx | 2 | 1 | | 35 | 35 | xxxxxxx | 2 | 1 | | 36 | 36 | xxxxxxx | 2 | 1 | | 37 | 37 | xxxxxxx | 2 | 1 | | 38 | 38 | xxxxxxx | 2 | 1 | | 39 | 39 | xxxxxxx | 2 | 1 | | 40 | 40 | xxxxxxx | 2 | 1 | +----+---------+---------+-------------+--------+ 30 rows in set (0.00 sec) Database changed mysql> explain select * from Sms_rr where phoneNo >20 and phoneNo<30; +----+-------------+--------+-------+---------------+-------------+---------+------+------+-----------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+-------------+---------+------+------+-----------------------+ | 1 | SIMPLE | Sms_rr | range | Sms_rr_idx1 | Sms_rr_idx1 | 4 | NULL | 1 | Using index condition | +----+-------------+--------+-------+---------------+-------------+---------+------+------+-----------------------+ 1 row in set (0.00 sec) Session 1: mysql> select connection_id(); +-----------------+ | connection_id() | +-----------------+ | 1 | +-----------------+ 1 row in set (0.00 sec) mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select * from Sms_rr where phoneNo >20 and phoneNo<30 for update; +----+---------+---------+-------------+--------+ | sn | phoneNo | message | channelType | status | +----+---------+---------+-------------+--------+ | 23 | 23 | ttt | 2 | 3 | +----+---------+---------+-------------+--------+ 1 row in set (0.00 sec) Session 2: mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(20,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(19,'cc',1,1); Query OK, 1 row affected (0.00 sec) mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(21,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(22,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(23,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(24,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(25,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(26,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(27,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(28,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(29,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(30,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(31,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(32,'cc',1,1); Query OK, 1 row affected (0.00 sec) 鎖住的區間爲【20,31】 next-key locks(gap 加上index-record)測試2: Session 1: Database changed mysql> start transaction; Query OK, 0 rows affected (0.00 sec) mysql> select connection_id(); +-----------------+ | connection_id() | +-----------------+ | 2 | +-----------------+ 1 row in set (0.00 sec) mysql> select * from Sms_rr where phoneNo = 23 for update; +----+---------+---------+-------------+--------+ | sn | phoneNo | message | channelType | status | +----+---------+---------+-------------+--------+ | 23 | 23 | ttt | 2 | 3 | +----+---------+---------+-------------+--------+ 1 row in set (0.00 sec) Sesssion 2: mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(19,'cc',1,1); Query OK, 1 row affected (0.01 sec) mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(20,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(21,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(22,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(23,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(24,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(25,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(26,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(27,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(28,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(29,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(30,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(31,'cc',1,1); ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> insert into Sms_rr(phoneNo,message,channelType,status) values(32,'cc',1,1); Query OK, 1 row affected (0.00 sec) 鎖了【20 31】這個區間