MySQL中事务的隔离性

隔离性是事务具有的特征之一。事务的四大特征,即ACID

原子性:通过redo log来保证

持久性:通过undo log来保证

隔离性:通过MVCC和锁来保证

一致性:通过原子性+持久性+隔离性一起保证

本文我们只谈事务的隔离性

因为MySQL支持多个客户端同时连接,所以在大多数业务场景下会有多个事务对数据进行操作。当多个事务并行的时候就难会出现一下问题场景:

脏读:一个事务读到了另外一个没有提交事务修改的数据,导致两次读取结果不同。即余额为100,A事务启动第一次读余额为100,此时B事务启动,修改余额为200,A事务第二次读数据变成了两百,B事务提交,A事务提交。

不可重复度:一个事务读到了另外一个已经提交事务修改的数据,导致两次读取结果不同。即余额为100,A事务启动第一次读余额为100,此时B事务启动,修改余额为200,B事务提交,A事务第二次读数据变成了两百,A事务提交。

幻读:一个事务查询第一次和第二次结果不同。AB事务同时在工作,分别查询年龄大于23岁的数据,此时查询结果都为10。然后A事务插入了一条数据,该数据的年龄字段大于23,然后提交了事务,此时B事务再次查询,数据年龄大于23的数据变为了11条。

面对这几种情况,MySQL分别提供了四种隔离级别来解决这些问题,不过级别越高,效率越低。读未提交:一个事务可以看到别的事务未提交的数据。这种隔离级别会出现脏读,不可重复读,幻读现象。

读提交:一个事务只能看到别的事务提交后的数据。这种隔离级别会出现不可重复读,幻读的现象。

可重复度:一事务所能看见的数据,与它刚开启的时候所看到的是一致的。这种隔离级别有可能出现幻读。

串行化:当一个事务对数据进行操作的时候会对这些数据加锁,只有当它操作完后别的事务才能对这些数据进行操作。

在MySQL中,虽然串行化的安全性最高,但是加锁的操作却使效率大大降低,所以一般不会采用串行化的级别。而可重复读在大多数场景下是能够解决幻读的。

针对快照读(普通select语句),MySQL用MVCC来避免解决幻读问题。

针对当前读(select...for  update等),MySQL用next-key lock(记录锁+间隙锁)的方式来避免。

MVCC在是通过read view(数据快照来实现的),可以理解为一个数据快照,将某一刻的数据定格住。读提交和可重读读都是基于MVCC来实现的,只不过创建read view的时机不同而已。读提交在执行每一条语句的时候创建一个read view,而可重复读只在事务开启的时候创建。

那么read view具体是怎样工作的呢?它其中有四个属性:

m_ids:它是一个数组,是一个记录着创建read view的时候,数据中所有活跃事务的id的数组。

min_trx_id:即m_ids中最小事务的id。

max_trx_id:即开启事务的时候,数据库中下一个事务的id。并不是m_ids中最大的id,应该是m_ids中最大的id+1。

creator_trx_id:即创建这个read view的事务的id。

而它具体的工作过程,就不得不提起聚簇索引当中的两个隐藏字段了:

trx_id:当某个事务对聚簇索引进行改动时,聚簇索引将会把这个事务的id记录在trx_id中。

roll_pointer:是一个指针,指向undo log中聚簇索引在被动之前的版本。

这样的话,对于这个read view来说,数据库当中事务就只有三种类型了。第一种,小于m_ids中最小的id的事务(即在read view开启之前,已经提交的事务),对于这些事务版本的数据该事务时可见的。对于在m_ids里面的事务,对于这些事务,在创建read view的时候还未提交,所以这些数据就是不可见的。对于大于m_ids中最大id的事务,说明在创建read view的时候事务还未开启,所以对于该事务也是不可见的。

在可重复读下

假设此时开启了一个新事务A,它的事务id是10,此时数据库中没有其他事务,那么m_ids中就只有10一个数字,这个事务所创建的read view, min_trx_id=10,max_trx_id=11,creator_trx_id=10,m_ids=[10]。

在事务A提交之后,又开启了一个B事务,此时B事务的m_ids=[10,11],min_trx_id=10,max_trx_id=12,creator_trx_id=11。

而在事务A之前又有事务C,为数据库中插入了一条余额=100的值,事务C的id=9,它在事务A开启之前已经提交了

此时A事务查询,其余额属性等于100,那么此时该行中trx_id=9,roll_pointer为空。然后B事务将余额修改为200,那么此时trx_id就变成了11,roll_pointer则指向上一条trx_id=10时候的版本此时事务A再次对该条记录做查询操作,先去读取trx_id的值,发现trx_id的值等于11,然后在自己的read view的m_ids中发现11大于m_ids中的最大值,即判定该条数据是一个在自己开启的时候,还没有开启的事务,所以就不会去查这条数据,而是去roll_pointer中找该数据的上一个版本.然后再拿m_ids做比较,发现9不在自己的m_ids中且小于min_trx_id,所以就判定这条数据在自己开启事务之前,就是一个可读的数据。

在读提交下

和可重复读是一样的,只不过读提交会在每执行一个语句之前都做一个read view。

假设此时开启了一个新事务A,它的事务id是10,此时数据库中没有其他事务,那么m_ids中就只有10一个数字,这个事务所创建的read view, min_trx_id=10,max_trx_id=11,creator_trx_id=10,m_ids=[10]。

在事务A提交之后,又开启了一个B事务,此时B事务的m_ids=[10,11],min_trx_id=10,max_trx_id=12,creator_trx_id=11。

而在事务A之前又有事务C,为数据库中插入了一条余额=100的值,事务C的id=9,它在事务A开启之前已经提交了。

假设此时B执行查询,这个时候会重新生成一个read view。在A事务将余额修改为200后提交了,它再次读取,read view变为了,m_ids=[11],min_trx_id=11,max_trx_id=12,creator_trx_id=11。此时trx_id=10小于min_trx_id,所以这条数据就是一条已经提交过的数据,就判定自己可以读这条数据。

可重复的与读提交都是以trx_id来和m_ids中的值做对比,从而判断操作该条数据的事务是不是在开启read view之前。唯一不同的是read view的开启时机,这里还需要再好好梳理一下的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不懂Java0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值