MySQL事物特性、隔离等级、MVCC

文章详细阐述了数据库事务的四大特性——原子性、一致性、隔离性和持久性,并重点讨论了InnoDB引擎如何实现这些特性,包括undolog、redolog、锁和MVCC(多版本并发控制)。同时,文章提到了并发事务中的问题,如脏读、不可重复读和幻读,以及InnoDB的不同隔离等级。MVCC通过ReadView确保事务的可见性,解决了并发读写的问题。
摘要由CSDN通过智能技术生成

事务特性

  1. 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成
  2. 一致性(Consistency):事务操作前后,数据的完整性约束,数据库应该保持一致性。举例来说就是A转账给B的事务前后,AB资产之和不变。
  3. 隔离性(Isolation):数据库支持并发事务,隔离性用来描述不同事务运行期间,他们数据视图之间不受彼此影响。
  4. 持久性(Durability):事务处理结束后,对数据的修改是永久的,系统故障后也不能丢失。

InnoDB引擎实现四个特性的技术

  1. 原子性:undo log
  2. 持久性:redo log
  3. 隔离性:锁 + MVCC
  4. 一致性:原子性+持久性+隔离性

并发事务的隔离性问题

  1. 脏读:A事务运行期间,读到了B事务尚未提交的修改。
  2. 不可重复读:A事务运行期间,对于同一数据两次查询结果不同。
  3. 幻读:事务运行期间,对于符合某个条件的记录集,两次查询结果中「记录数量」发生了变化。

InnoDB的隔离等级

是否解决脏读不可重复读幻读
读未提交 RU
读已提交 RC
可重复读 RR✅(InnoDB部分解决
串行化 Serializable

MVCC

我不知道有没有人和我一样,多线程学多了之后脑子里就全是线程安全问题,任何东西都要扯上线程安全性。如果你没有,那么恭喜你,你至少没有强迫症,不至于想到头昏脑胀。
反正,在初次看MVCC相关的知识的时候,隐藏字段、ReadView真是把我头搞昏了,本身并不难,但是我喜欢扯上线程安全问题,搞得头昏。
总之,MySQL对于事务创建、事务提交、维护活跃事务这一块,有互斥保护就完事了。
接下来正式进入MVCC。

快照读 vs 当前读

数据操作方式说明对应SQL
快照读基于多版本并发控制,本质上是基于记录的版本链,实际读取的是版本链上记录,也就可能是历史记录普通select
当前读这个名字取的莫名其妙的,而且实际上不止是读,还可能写,反正只要记住他会对当前数据集加锁就完事select … lock in share mode🍎select … for update; 🍎 update;🍎 insert; 🍎delete

数据库并发场景已经解决策略

  1. 读-读:这个不用管,全读没有并发问题
  2. 读-写:我们在隔离级别里面举的例子,基本都是读写场景,这个需要处理,解决方法是锁+MVCC。
  3. 写-写
    3.1 第一类丢失更新-回滚丢失(AB同时开启事务,B提交了,但是A却后面回滚了,导致B的更新没了,这个所有隔离级别中都不会出现,因为回滚还要看undo log的操作事务ID)
    3.2 第二类丢失更新-覆盖丢失 (AB同时开启事务,AB读出一样的记录,B先更新,接着A又更新了,这下B更新又丢了,这个需要通过加锁解决【悲观 & 乐观】)

针对上面的读写情况,我有几句想说的:如果不谈数据库,我们普通写个读写并发的场景,就比如Java中的读写锁这种,显然读和写是互斥的,但是MySQL中,读不一定要和写互斥,因为MySQL中的隔离性规定了读的特殊性,你看那几个隔离级别,几乎都是和读这个字有关系的。在实际的MySQL读中,读的不一定是目前记录的最新版本,正是因为这个特殊的规定,MySQL普通读可以不加锁,通过MVCC和版本链来读取。

undo log 和版本链

undo log是用来保证事务原子性的一份日志文件,保存了记录的历史版本。实际上,事务内部对于记录的修改,是直接反映到聚簇索引上的记录的,只不过这些新记录会有隐藏字段指到undo log中的历史记录,便于回滚。
每行记录都有几个隐藏字段:

  1. db_trx_id:最近修改/插入这条记录的事务ID
  2. db_roll_pointer:回滚指针,指向上一个版本(在rollback segment中)
  3. db_row_id:隐式主键,没有指定主键的时候才生成。
    在这里插入图片描述

Read View

事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照。
Read View主要是用来做版本链中的记录可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View实际字段名字不重要,反正也记不住。。。
只要记住有这几部分:

  1. 当前所有活跃事务ID (活跃指的是开启后尚未提交/回滚)
  2. 下一次将会分配的事务ID(事务ID分配是自增的,当前时刻的下一时刻如果要创建一个事务,就对应这个ID)
  3. min(当前所有活跃事务ID) 【min是最小值的意思】
  4. 创建这个ReadView的事务ID
    在这里插入图片描述
    上图中,当前活跃id为102和103,最小的是102,下一次会分配的事务ID是104,创建这个ReadView的事务ID是103。

判断记录可见性的算法

我们知道每行记录都有隐藏字段db_trx_id标记最近修改/插入他的事务ID,现在就是拿这个db_trx_id来和Read View里面的字段做对比,依此来判断创建这个ReadView的事务能否看到这个对应的记录。
注意下面的算法是依次进行判断的(if-else格式的):

  1. db_trx_id < min(当前活跃的事务ID) || db_trx_id == 创建这个ReadView的事务ID
    注意上述两个表达式的右边都是ReadView字段,里面所提及的事务ID所在时间节点应该是ReadView被创建的时候。
    这样的话,第一个表达式,当前记录操作ID小于创建ReadView时候的时候存在的所有ID,说明这玩意早就被提交掉了,当然可见;第二个表是,当前记录操作ID等于创建ReadView的事务ID,一个事务内部那肯定也可以看见。
  2. db_trx_id >= 下一次将会分配的事务ID
    说明这个记录的操作事务ID的产生是后于我们创建ReadView的时刻的,那么这个是不可见的。
  3. 剩余情况,只剩下 db_trx_id >= min(当前活跃的事务ID)&&db_trx_id < 下一次将会分配的事务ID && db_trx_id != 创建这个ReadView的事务ID:此时要看 db_trx_id在不在活跃事务id集合中,如果在的话,由于事务隔离性,当前活跃的其他事务我们看不见,如果不在的话说明已提交,可以看见。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值