MySQL篇之当前读和快照读有什么区别?

在学习小林coding---MySQL篇中,MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种:

  • 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好了避免幻读问题。
  • 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。

 此学习章节总结整理"当前读和快照读"相关知识如下:

1、mysql读取数据实际上有两种读取模式:当前读和快照读

  • 当前读:每次读取的都是当前最新的数据,但是读的时候不允许写,写的时候也不允许读。
  • 快照读:读写不冲突,每次读取的是快照数据,
    • 隔离级别Repeatable Read下(默认隔离级别):有可能读取的不是最新的数据
    • Read Committed隔离级别下:快照读和当前读读取的数据是一样的,都是最新的。

 2、相关知识 

  回滚日志undo log:在操作数据之前,把需要操作的数据和事务记录备份到undo log中,目的就是为了保证原子性,如果一个事务执行中发生了故障,就可以通过undo log进行回滚。

  • 共享锁(S锁):共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。

    • 如果事务T仅对数据A进行读取,那么会对数据A加上共享锁,之后则其他事务如果要读取数据A的话可以对其继续加共享锁,但是不能加排他锁(也就是无法修改数据)。获准共享锁的事务只能读数据,不能修改数据
  • 排他锁(X锁):用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。

    • 如果事务T对数据A要进行修改,则需要对其添加排它锁,加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据

 MVCC:全称Multi-Version Concurrency Control(感兴趣的小伙伴可以参考学习这个全网最全的一篇数据库MVCC详解,不全我负责-mysql教程-PHP中文网

  • 多版本并发控制,说的通俗易懂一点就是记录数据的不同版本

 3、当前读

 当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务 不能修改当前记录,会对读取的记录进行加锁。

  • Mysql实现当前读是通过共享锁+排他锁+Next-Key Lock实现的。
  • 每次对行数据进行读取的时候,加共享锁。此时就不允许修改,但是允许其他事务读取,所以每次都可以读到最新的数据。
  • 每次对行数据进行修改的时候,加排他锁,不允许其他事务读取和修改。这种情况下其他事务读取的数据也一定是最新的数据。
  • 每次对范围行数据进行读取的时候,对这个范围加一个范围共享锁。
  • 每次对范围行数据进行修改的时候,读这个范围加一个范围排它锁。
  • 加锁的 SELECT,或者对数据进行增删改都会进行当前读。 比如:

基于上述锁机制,实现当前读,确保每次读取的都是最新的数据。

基于上述分析,快速判断执行的语句是当前读还是快照读:

在默认隔离级别下,select 语句默认是快照读 select a from t where id = 11

update 语句、select 语句加锁是当前读,例如:

  • select a from t where id = 1 for update;#排他锁
  • select * from student where ...
  • select * from student lock in share mode; # 共享锁
  • select * from student where id = 1 lock in share mode; # 共享锁
  • select * from student for update; # 排他锁
  • insert into student values ... # 排他锁
  • delete from student where ... # 排他锁
  • update student set ... # 排他锁

4、快照读 

又叫一致性读,读取的是快照数据,当某个数据正在被修改的时候,也可以进行读取该数据,保证读写不冲突。不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞 读;比如: 之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下, 避免了加锁操作,降低了开销。 既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。 快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

  • mysql中的快照读是通过MVCC+undolog实现的。
  • 刚刚提到undolog,当对记录做了变更操作时,就会产生undo记录,undo记录中存储的是老版数据,当一个旧的事务需要读取数据时,为了能够读取到老版本的数据,需要顺着undo列找到满足其可见性的记录,这个找满足可见行的记录依赖。就是说每次都是读取undolog中的数据。
  • 实际上,不同事务或者相同事务的对同一记录的修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。所以快照读都是去读取undolog中链首的最新的旧记录。

 不同隔离级别下的快照读有所区别

  • 事务开始前 name = 张三 id = 1
  • 事务A:select name from t where id = 1 (多次)
  • 事务B:update name = 李四 from t where id = 1 

在Read Committed隔离级别下,快照读和当前读读取的是一样的。因为每次select都会去读取最新的快照数据。

过程如下:

  • 事务A开始
  • 事务B开始
  • 事务A select ,查出name = 张三
  • 事务B update,修改name = 李四
  • 事务B结束
  • 事务A 再次select ,查出name = 李四 (因为此时undolog中的最新数据为李四)
  • 事务A结束

在默认隔离级别Repeatable Read下,在一个事务内,读取的都是第一次select的数据,所以可能会出现读取的数据不是最新数据的情况
过程如下:

  • 事务A开始
  • 事务B开始
  • 事务A select ,查出name = 张三
  • 事务B update,修改name = 李四
  • 事务B结束
  • 事务A 再次select ,查出name = 张三 (因为只读第一次的快照数据)
  • 事务A结束

 

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天努力学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值