MySQL事务隔离级别

前言

没什么前言,直接开始吧!
主要想从下面三个方面说一下事务隔离级别:

  1. 为什么会出现事务隔离级别。
  2. 事务隔离级别的种类。
  3. 如何实现事务隔离级别

为什么会出现事务隔离级别?

事务隔离隔离级别,顾名思义,就是事务的隔离级别,主要服务对象就是事务。
事务在高并发的情况下会暴露很多问题,主要有三种:

  1. 脏读
    一个事务读取到另一个事务未提交的数据,别的行未提交的数据也叫脏数据,所以叫脏读
  2. 不可重复读
    在同一个事务中多次读取同一个数据,出现数据不一致,重复读取出现问题,所以叫不可重复读
    不可重复度可以看做脏读的升级版本,读到了别的事务已提交的数据。
  3. 幻读
    在同一个事务中多次读取一定范围内的数据,出现数据行不一致的情况,像是出现幻觉一样,所以叫幻读
    幻读在不可重复读的基础上再上升了一档,不但当前行数据出现了不一致,查找范围内也出现了不一致。

这里比较难区分的是不可重复读和幻读,记住它们两个针对对象不一致即可。

  • 不可重复读针对的主要是单行内的数据,比如在同一个事务中读取id为1的name为a,过一段时间再读取id为1的name却变为b了,就是不可重复读。
  • 幻读针对的主要是一定范围内的数据,比如在同一个事务中读取id大于1但小于10的数据只有三条数据,过一段时间再读取这个范围却变成四条数据,就是幻读。

MySQL是一款支持并发的数据库软件,肯定要解决上面的问题的啊,所以就衍生出了事务隔离这种东西,针对不同的问题,对事务隔离又分了级,就是事务隔离级别

事务隔离级别的种类

针对上面的三个问题,数据库中存在四种事务隔离级别

  1. 未提交读(READ_UNCOMMITTED)
    最低的事务隔离级别,脏读都不能避免。
  2. 已提交读(READ_COMMITED)
    在这个隔离级别下可以避免脏读,但不能避免不可重复读。
  3. 可重复读(REPEATABLE_READ)
    在这个隔离级别下可以避免不可重复读,但不能避免幻读。
  4. 串行化(SERLALIZABLE)
    最高的隔离级别,可以避免并发产生的问题。
事务隔离级别避免脏读避免不可重复读避免幻读
未提交读×××
已提交读××
可重复读×
串行化

(在InnoDB中有点特殊,可重复读级别也能在一定程度上避免幻读)
天下没有免费的午餐,越高的隔离级别在进行操作时开销越大,支持的并发越低,为了针对不同的业务场景,MySQL才会对其进行分级。


各隔离级别特性不需要硬记,从中文命名上基本上都能看出来。


事务隔离级别如何实现

这里说的是InnoDB事务隔离级别的实现,毕竟InnoDB是MySQL中支持事务最优秀的存储引擎


未提交读

这个没什么好说的,基本没采取什么措施。不过支持的并发度最高,在不更新数据的时候用这个隔离级别是非常不错的选择。


已提交读和可重复读

这两个可以放在一块说,因为实现的原理差不多。


有锁经验的可能能想到,直接对查询的数据行使用共享锁不就可以防止脏读和不可重复读的问题了吗?的确,直接对数据行使用行锁能有效的避免脏读和不可重复读的问题,但是这也就限制了其他想要修改这行数据的事务。
可能有些人想问了,你想避免脏读和不可重复读不锁住行怎么行?InnoDB就是想不锁住行,还把问题给解决了。
如何在不锁住行的情况下还能避免脏读呢?这就涉及到两个概念:当前读快照读。这两个可以这样理解:

  • 当前读:就是上面上锁的思想,读取数据时顺便将数据行加锁,使其它事务不能修改数据,自然不会产生脏读和不可重复读的情况。
  • 快照读:和名字一样,就是对数据进行进行一次快照读取数据中的内容。因为不需要加锁,又叫非阻塞读。
    为了能支持更高的并发,已提交读和可重复读的隔离级别select只要没有显式加锁(显式加锁:select * from TABLE lock in share mode),采用的就是快照读。只不过采用的策略不一样.
  • 已提交读每次读取都到特定事务版本的数据,没有提交的数据不会对这儿的数据造成影响,这样避免了脏读的发生。
  • 可重复读对特定事务版本的数据进行快照之后,会将读取到的数据快照保存在了另一个地方,读取读取过的数据就会从这个地方中读取(不是口胡,有点绕)。因为事务提交也不会对另存的地方造成影响,这样就避免了不可重复读的发生。
    (可以理解为对数据做了一个备份,备份与原数据没有太大的关联,但注意的是不是将所有数据都做一个备份,那样需要耗费的空间太大,只是将查询过的数据行进行备份,比如查询id为1的数据行,并不会对id为2的数据行进行备份)

快照的实现原理主要涉及以下几个概念,这里就不展开来讲了。

  1. 数据行中的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID
    • DB_TRX_ID:事务id,InnoDB中事务都有一个id,这个id是递增的,和事务开启的时间有关系。也就是说,越早开始的事务这个id越小。
    • DB_ROLL_PTR:回滚指针,记录着事务开始的时候在undo日志的位置。
    • DB_ROW_ID:行号。
  2. undo日志
    当我们对数据做了变更操作的时候,就会产生undo记录,undo记录的集合就是undo日志。
    undo中记录的都是更改之前的数据,当我们需要提取事务开始之前的数据时,就需要到undo日志中去查找。
  3. read view
    read view是事务开启时,当前所有事务的一个集合。
可重复读级别下的幻读的防止

InnoDB使用next-key锁保证防止幻读的产生。


next-key锁可以看成由行锁Gap锁两部分组成。行锁不用说了,主要说下Gap锁。


Gap锁

又称间隙锁,顾名思义,就是针对间隙的锁。使用Gap锁的时候是会对数据周围的间隙锁住。这个间隙与数据有关。比如果数据中有:1、3、5、7、9五个数据,那么就可以对下面几个间隙进行锁定:

(-oo, 1]、(1, 3]、(3, 5]、(5, 7]、(7, 9]、(9, +oo]

比如你查询3这个数据行,那么这个事务未提交前其他事务就不能添加大于1小于等于5的数据(假设这个数据不是主键或唯一索引,主键或唯一索引有些特别,待会说)。添加值为4的数据行就会被阻塞住。


查询主键和唯一索引会不会使用Gap锁还得看情况,主要有两种情况:

  • 如果where条件全部命中,则不会用Gap锁,只会加记录锁。
    全部命中:精确查询时所有记录都存在。
  • 如果where部分命中或者全部不命中,则会加上Gap锁。

这也是为了性能考虑,加锁总是得付出代价的,能不加就不加。

为什么主键或唯一索引where条件全部命中可以不用加Gap锁呢?
如果全部命中,InnoDB就会对数据加上记录锁,这个事务提交前别的事务不能删除这些行。因为是唯一索引,所以也不可能添加一样的数据,所以使用记录锁完全满足条件。

串行化

这个是最高的隔离级别,所有的select采用的都是当前读,都会对数据上行锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值