1. 隔离级别

😀未提交读

  • 读到其它事务未提交的数据(最新的版本)

  • 错误现象:有脏读、不可重复读、幻读现象

脏读现象

tx1tx2
set session transaction isolation level read uncommitted;
start transaction;
select * from account; /两个账户都为 1000/
start transaction;
update account set balance = 2000 where accountNo=1;
select * from account; /1号账户2000, 2号账户1000/
  • tx2 未提交的情况下,tx1 仍然读取到了它的更改

😀提交读(RC

  • 读到其它事务已提交的数据(最新已提交的版本)

  • 错误现象:有不可重复读、幻读现象

  • 使用场景:希望看到最新的有效值

不可重复读现象

tx1tx2
set session transaction isolation level read committed;
start transaction;
select * from account; /两个账户都为 1000/
update account set balance = 2000 where accountNo=1;
select * from account; /1号账户2000, 2号账户1000/
  • tx1 在同一事务内,两次读取的结果不一致,当然,此时 tx2 的事务已提交

😀可重复读(RR

  • 在事务范围内,多次读能够保证一致性(快照建立时最新已提交版本)

  • 错误现象:有幻读现象,可以用加锁避免

  • 使用场景:事务内要求更强的一致性,但看到的未必是最新的有效值

幻读现象

tx1tx2
set session transaction isolation level repeatable read;
start transaction;
select * from account; /存在 1,2 两个账户/
insert into account values(3, 1000);
select * from account; /发现还是只有 1,2 两个账户/
insert into account values(3, 5000); /* ERROR 1062 (23000): Duplicate entry '3' for key 'PRIMARY' */
  • tx1 查询时并没有发现 3 号账户,执行插入时却发现主键冲突异常,就好像出现了幻觉一样

加锁避免幻读

tx1tx2
set session transaction isolation level repeatable read;
start transaction;
select * from account; /存在 1,2 两个账户/
select * from account where accountNo=3 for update;
insert into account values(3, 1000); /* 阻塞 */
insert into account values(3, 5000);
  • 在 for update 这行语句执行时,虽然此时 3 号账户尚不存在,但 MySQL 在 repeatable read 隔离级别下会用间隙锁,锁住 2 号记录与正无穷大之间的间隙

  • 此时 tx2 想插入 3 号记录就不行了,被间隙锁挡住了

😀串行读(Serializable​​​​​​​

  • 在事务范围内,仅有读读可以并发,读写或写写会阻塞其它事务,用这种办法保证更强的一致性

  • 错误现象:无

串行读避免幻读

tx1tx2
set session transaction isolation level serializable;
start transaction;
select * from account; /* 存在 1,2 两个账户 */
insert into account values(3, 1000); /* 阻塞 */
insert into account values(3, 5000);
  • 串行读隔离级别下,普通的 select 也会加共享读锁,其它事务的查询可以并发,但增删改就只能阻塞

2. 快照读与当前读

😀当前读

即读取最新提交的数据

  • select … for update

  • select ... lock in share mode

  • insert、update、delete,都会按最新提交的数据进行操作

当前读本质上是基于锁的并发读操作

😀快照读

读取某一个快照建立时(可以理解为某一时间点)的数据,也称为一致性读。快照读主要体现在 select 时,而不同隔离级别下,select 的行为不同

  • 在 Serializable 隔离级别下 - 普通 select 也变成当前读,即加共享读锁

  • 在 RC 隔离级别下 - 每次 select 都会建立新的快照

  • 在 RR 隔离级别下

    • 事务启动后,首次 select 会建立快照

    • 如果事务启动选择了 with consistent snapshot事务启动时就建立快照

    • 基于旧数据的修改操作,会重新建立快照

快照读本质上读取的是历史数据(原理是回滚段),属于无锁查询

RR 下,快照建立时机 - 第一次 select 时

tx1tx2
set session transaction isolation level repeatable read;
start transaction;
select * from account; /* 此时建立快照,两个账户为 1000 */
update account set balance = 2000 where accountNo=1;
select * from account; /* 两个账户仍为 1000 */
  • 快照一旦建立,以后的查询都基于此快照,因此 tx1 中第二次 select 仍然得到 1 号账户余额为 1000

如果 tx2 的 update 先执行

tx1tx2
set session transaction isolation level repeatable read;
start transaction;
update account set balance = 2000 where accountNo=1;
select * from account; /* 此时建立快照,1号余额已经为2000 */

RR 下,快照建立时机 - 事务启动时

如果希望事务启动时就建立快照,可以添加 with consistent snapshot 选项

tx1tx2
set session transaction isolation level repeatable read;
start transaction with consistent snapshot; /* 此时建立快照,两个账户为 1000 */
update account set balance = 2000 where accountNo=1;
select * from account; /* 两个账户仍为 1000 */

RR 下,快照建立时机 - 修改数据时

tx1tx2
set session transaction isolation level repeatable read;
start transaction;
select * from account; /* 此时建立快照,两个账户为 1000 */
update account set balance=balance+1000 where accountNo=1;
update account set balance=balance+1000 where accountNo=1;
select * from account; /* 1号余额为3000 */
  • tx1 内的修改必须重新建立快照,否则,就会发生丢失更新的问题。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐