建表SQL
CREATE TABLE `user` ( `id` int(11) NOT NULL COMMENT 'id', `balance` int(255) DEFAULT NULL COMMENT '余额', `version` int(255) DEFAULT NULL COMMENT '版本号', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_croatian_ci;
当前的MYSQL的事务隔离级别为,REPEATABLE-READ
select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+
尝试触发幻读
SESSION1 | SESSION2 |
---|---|
start transaction; |
start transaction; |
select * from user; |
|
insert into user values(1, 0, 0); |
|
commit; |
|
select * from user; |
|
commit; |
|
select * from user; |
- session1开启事务,并且执行一次检索,记录为空
- session2开启事务,并且插入一条新记录,提交
- session1执行检索,未读取到session2插入的新记录,记录还是为空
- session1提交,再次检索可以成功读取到session2插入的新记录
说明 MYSQL 的 REPEATABLE-READ , 其实是可以解决幻读的问题
但是,不一定能彻底的解决幻读的问题
SESSION1 | SESSION2 |
---|---|
start transaction; |
start transaction; |
select * from user where version = 1; |
|
insert into user values(2, 0, 1); |
|
commit; |
|
select * from user where version = 1; |
|
update user set balance = 1 where version = 1; |
|
select * from user where version = 1; |
|
commit; |
- session1开启事务,并且根据条件
version = 1
执行一次检索,记录为空 - session2开启事务,并且插入一条符合session1检索条件的新记录,提交
- session1再次尝试根据条件
version = 1
进行检索,未读取到session2插入的新记录,记录还是为空 - session1尝试修改session2新插入的记录,修改成功
- session1执行修改成功后,再次根据条件
version = 1
进行检索,发现读取到了session2新插入的记录
第一种情况可以解决幻读,而第二种情况就不能解决幻读,为什么呢?
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。 快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。 当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。 在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例: 快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析) select * from table where ?; 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。 select * from table where ? lock in share mode; select * from table where ? for update; insert into table values (…); update table set ? where ?; delete from table where ?; 所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,
其他的操作,都加的是X锁 (排它锁)。
MySQL/InnoDB定义的4种隔离级别:
-
Read Uncommited
可以读取未提交记录。此隔离级别,不会使用,忽略。
-
Read Committed (RC)
快照读忽略,本文不考虑。
针对当前读,RC隔离级别保证对读取到的记录加锁 (记录锁),存在幻读现象。
-
Repeatable Read (RR)
快照读忽略,本文不考虑。
针对当前读,RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),不存在幻读现象。
-
Serializable
从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。