架构设计事务篇之Mysql事务原理

avatar

概述

事务是一组读写操作,这些操作被当作一个独立的工作单元被执行,这些操作的执行结果要么全部成功,要么全部失败,不允许部分成功、部分失败的情况出现。
在数据库中,事务的作用是为了保障数据的一致性,即:操作结果和期望的结果一致,它有四个特性:原子性、一致性、隔离性、持久性。

实列

事务最典型的一个例子是转账,假设我们有一个账户表,表中有两条记录,一条是A账户的记录其余额为100,另一条是B账户的记录其余额为50。
这时,A账户发起了一笔转账到B账户的请求,其金额为50;此时该转账操作便是一个事务,它包含两个写操作,一个是A账户扣减50,另一个是B账户增加50;
这两个操作是不可分割的,不能出现只执行其中一个而不执行另一个,也不能出现其中一个失败另一个成功,否则执行结果就和我们期望的结果不一致。
比如,A执行成功,B执行失败,那么结果是A账户余额为:50,B账户余额还是:50,这样就和我们业务上期望的结果A为:50、B为:100不一致了。
两个操作对应的数据库Sql语句如下:


//扣减A账户金额
update account set balance=balance-50 where id="A";
//增加B账户金额
update account set balance=balance+50 where id="B";

问题

因此,为了保障事务的一致性,我们首先要保重的是事务中的操作要么都成功要么都失败不能出现其它情况,而且当其中某些操作失败时,该事务中其它成功的操作需要被撤销使数据恢复到事务执行前的状态,
这便是我们碰到的第一个问题:需要保证事务的原子性。
其次,因为数据操作都是先从磁盘加载到内存,然后在内存进行数据更新,最后同步到磁盘,这就引发了操作执行后数据持久性的问题,即:如何保证事务成功后重启数据库后数据状态还是期望的状态。
最后,因为现代处理器的架构都是多核架构,在支持多个事务并行处理的情况下,如何保证事务之间不相互影响,这便是事务之间隔离性的问题,比如说:同一时刻有两个事务给B账户转账50,如果两个事务在执行过程中获取的账户金额都是50,那么最后的结果便是100,而不是150,这就和期望的结果不一致了;

所以,为了保证数据的一致性,我们需要事务保证其操作执行的原子性和隔离性以及操作结果即数据的持久性。

方案

下面我们以最常用的数据库Mysql为例,讲解一下它是通过什么方式保证事务的原子性、隔离性以及持久性的,从而实现数据的一致性。

原子性

原子性所要解决的关键问题是事务执行失败时,已经执行成功的操作如何撤销或者说如何将已经被更改的数据恢复到该事务开始执行前的状态。
Mysql使用的是undo日志,事务开始后,如果要修改某一条记录,Mysql会先将该记录的原始值保存在undo日志中并同步到磁盘,接着才是修改记录,最后提交事务。

比如,我们只执行下面这条Sql语句:

update account set balance=balance-50 where id="A";

那么其事务实现过程大致如下:

1、开始事务A
2、将balance原始值100存储到undo日志中
3、将balance修改为50
4、提交事务

在上面的事务的执行过程中,如果事务执行到第三步出现了异常,那么可以通过undo日志恢复内存中的数据,如果在这之前出现异常什么都不需要做因为日志数据和原始数据是一致的。

持久性

持久性所面临的问题是如何将内存中的数据高效地同步到磁盘,如上面的例子最简单的方法就是在第三步后第四步前,将balance写入磁盘,如下:

1、开始事务A
2、将balance原始值100存储到undo日志中
3、将balance修改为50
4、将balance数据写入磁盘
5、提交事务

但是,如果存在多条变更的数据会因磁盘寻址耗时导致数据库处理事务的性能变低,所以Mysql采用redo日志来顺序记录变更的数据,这样减少了不必要的寻址。
等到,事务提交后,Mysql会使异步将redo中的数据线程

隔离性

隔离性所面临的问题是一个事务在执行的过程中是否允许另一个事务读取它正在修改的数据,如果不允许那么就得让事务串行化,这是最高的隔离级别,如果允许那么又有三种不同的隔离级别:读未提交、读已提交、可重复读。

读未提交:一个事务可以读取另一个事务未提交的数据,这种不采取任何隔离措施的策略会导致脏读、幻读、不可重读等问题,而且对数据一致性没有任何保障。

读已提交:一个事务可以读取另一个事务已经提交的数据,它避免了脏读,但还存在幻读、不可重复读的问题。

可重复读:同个事务多次读取相同的数据放回的结果是一样的,Mysql的可重复读可以解决脏读、幻读、不可重读的问题。

那什么情况下会发生脏读、幻读、不可重读呢?

脏读发生在一个事务A读取另一个事务B未提交的数据时,如果B最后因为失败回滚,那么A读取的数据就是无效的。

幻读发生在一个事务向数据库插入某条数据ID为X的记录是,它先select没有发现该记录,然后准备插入此时发现数据库已经存在该条记录,这条记录是另一个事务在其select之后提交的。

不可重复读发生在一条记录被并发修改,这样导致会导致某个事务多次读取的数据结果不一样。

扩展阅读

架构设计思维篇之结构

架构设计思维篇之概念

架构设计容错篇之重试

架构设计容错篇之熔断

架构设计容错篇之限流

架构设计事务篇之Mysql事务原理

架构设计事务篇之CAP定理

架构设计事务篇之分布式事务

架构设计消息篇之消息丢失

架构设计消息篇之保证消息顺序性

推荐阅读更多精彩内容