mysql默认隔离级别是可重复读,也就是说会有幻读问题
不可重复读和幻读意思很相近,不可重复读对于行而言,而幻读对于表而言
create table `account` (
`id` bigint(20) NOT NULL,
`balance` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4
COLLATE = utf8mb4_bin;
insert account values(1, 1000);
insert account values(2, 100);
insert account values(3, 10000);
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 100 |
| 3 | 10000 |
+----+---------+
select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
set session transaction isolation level read uncommitted;
start transaction;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 100 |
| 3 | 10000 |
+----+---------+
set session transaction isolation level read uncommitted;
start transaction;
update account set balance=balance+50 where id=2;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
rollback;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 100 |
| 3 | 10000 |
+----+---------+
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 100 |
| 3 | 10000 |
+----+---------+
若A使用了rollback之前的数据,就产生了脏读
set session transaction isolation level read committed;
select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
start transaction;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 100 |
| 3 | 10000 |
+----+---------+
set session transaction isolation level read committed;
start transaction;
update account set balance=balance+50 where id=2;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 100 |
| 3 | 10000 |
+----+---------+
数据并没有更改,解决了脏读问题
commit;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
符合B事务提交后的正常结果,但在A事务中,两次查询结果不相同,这就是不可重复读问题
set session transaction isolation level repeatable read;
start transaction;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
set session transaction isolation level repeatable read;
start transaction;
update account set balance=balance+50 where id=2;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 200 |
| 3 | 10000 |
+----+---------+
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
没有出现脏读问题
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 150 |
| 3 | 10000 |
+----+---------+
也没有出现不可重复读问题,原理是MVCC机制
start transaction;
insert account values(4, 500);
commit;
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 200 |
| 3 | 10000 |
| 4 | 500 |
+----+---------+
select * from account;
+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 200 |
| 3 | 10000 |
+----+---------+
并没有出现预想中的幻读问题
insert account values(4, 500);
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'
意思是该数据已经存在了,发生了幻觉
在实际使用过程中,一般使用命中主键的查询,并添加for update语句获取行级写锁
select * from account where id=4 for update;
这个锁直到commit才会被释放,在锁期间,所有对id=4的行的操作,包括select/update/insert都会阻塞,就算id=4的记录不存在,也会上锁
参看这篇文章
普通的select查询为快照读,而select for update为当前读,在RR等级下,只有第一次查询会起快照,update等操作影响不到该快照,RR和RC通过读哪次快照,区分了当前读/快照读