MySQL事务隔离

102 阅读5分钟

一、什么是事务

事务就是用户定义的一系列数据库操作,这些操作可以视为一个完成的逻辑处理工作单元,要么全部执行,要么全部不执行,是不可分割的工作单元。

二、为什么需要事务

假如存在一个转账场景,A向B转账1000元。这个操作简单来说可以分为2步:

  • A减少1000元
  • B增加1000元

通常想要实现上述2个操作,一条SQL语句是没有办法完成的。但2个操作又是一体的,假如第一个操作结束后程序崩溃掉,此时A减少了1000元但B没有增加,相当于有1000元白白丢失掉了。

为了解决上述类似的问题,引入了事务的概念。在一个事务内,不管有多少操作,只要当前事务未提交,所有的的操作都不会改变真实的数据库数据。当提交后,所有操作的结果都会生效,这种设计就保证了数据的一致性。

三、事务隔离等级

虽然事务保证了数据在数据库系统中的一致性,但这并没有保证数据在事务运行过程中的一致性。简单的说,事务A正在运行,并且使用了a数据,但事务B在某一刻更改了a数据,导致事务A在第二次使用a数据时发现前后数据不一致。这只是其中一种较为容易理解的情况,为了保证事务在执行过程中的数据一致性,因此事务必须隔离。隔离等级有以下四种:

  • 读未提交

    对数据没有任何限制,即事务A修改了某个数据但还未提交,事务B也能获取到修改后的数据。

  • 读已提交

    只能获取已经提交的事务数据。即事务A修改了某个数据并且已经提交,事务B才能获取到修改后的数据。

  • 可重复读

    这是在一个事务执行过程种,针对同一数据,不管有没有其他事务更改了数据,每次读取到的数据是一致的。

  • 串行化

    针对同一行数据记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

四、隔离等级-可重复读

在四个隔离等级中,“读未提交”不能保证数据准确性,“读已提交”不能保证数据一致性,“串行化”很容易导致阻塞,因此“可重复读”是最理想的隔离等级。

1.MVCC

要想实现“可重复读”,必须引入一个MVCC的概念。MVCC(Multi-Version Concurrency Control)是多版本并发控制的意思。简单来说,就是针对某个数据,在数据库中存在很多个版本。事务A对应A版本,事务B对应B版本,即便事务A和事务B都在运行且数据被更改了,但后续获取数据时只要去找到对应版本的数据,那么前后数据就是一致的。

2.一致性读视图(readview)

MVCC只是一种设计思想,那么是怎么实现的呢?这个时候要引入“一致性读视图”的技术。这个技术用于“读已提交”和“可重复读”隔离等级的实现,它的本质是一个事务数组。其实数据表中的每一行数据都存在不可视的隐藏字段,其中最为关键的2个用于实现readview的如下:

  • db_trx_id

    操作这个数据的事务ID,也就是最后一个对该数据进行插入或更新的事务 ID。

  • db_roll_ptr

    回滚指针,也就是指向这个记录的Undo Log信息。

Undo Log的原理如下:

image.png

3.readview的工作原理

当一个事务创建一个readview时,这个readview就是当前正处于活动状态的所有事务组成的数组。此时这个readview数组内存在一个最大事务ID最小事务ID。在读取某行数据时,如果行数据的事务ID字段值小于最小事务ID,表明修改数据的那个事务是在当前视图创建前就已经提交,因此数据可见。如果大于最大事务ID,表明事务是在创建视图之后提交的,因此数据不可见,如果大于最小事务ID小于最大事务ID,则遍历readview数组,如果不在数组内,则表明已经提交,数据可见,反之表明未提交,数据不可见

当读取到的数据不可见时,此时会根据Undo Log信息,查找上一个修改版本的数据,然后再一次做是否可见判断,通过这种多版本的控制,就能实现针对同一事务,在读数据上保证数据的一致性。

有的资料是说创建一个事务时就会创建一个视图,有的则是第一次读取时创建一个视图。这要分隔离等级:

  • 读已提交

    每一次读取数据时都会创建一个视图,因此前后数据可能不一致。

  • 可重复读

    可以通过SQL语句实现开启事务时创建一个视图,也可以在第一次读取时创建一个视图。

五、总结

事务隔离最关键的还是要理解MVCC的设计思想。常用的仓库工具git也是这种思想的实现之一。对于一个数据A,保存多个数据版本,每一个事务对这个数据的修改都产生一个全新的数据版本,因此当读取时,只需要去取当前事务使用的版本,这样就能保证同一事务之中数据的一致性。