• 进入"运维那点事"后,希望您第一件事就是阅读“关于”栏目,仔细阅读“关于Ctrl+c问题”,不希望误会!

MySQL InnoDB单机事务原理

MySQL 彭东稳 8年前 (2016-06-19) 20329次浏览 已收录 0个评论

一、事务简介

事务(transaction)是数据库区别于文件系统的一个重要特性,事务意为一系列操作的集合,具有 ACID 的特性,能将数据库从一个一致的状态转换到另一个状态。

简单说事务就是由一组 SQL 语句组成的一个执行单元,在这组执行单元中的所有语句要么全部执行成功,要么全部执行失败。

MySQL 中,InnoDB 存储引擎支持事务操作,这里也主要以 InnoDB 为主,其他支持事务的存储引擎有兴趣自行看。

二、单事务单元

银行应用是解释事务必要性的一个经典例子,现在假设有这么一个单事务单元,要从用户Bob的账户转移100元到Smith的账号,那么需要至少以下几个步骤(按照事务时间序)。

1步:锁定Bob的账户

2步:锁定Smith的账户

3步:检查Bob的账户是否100

4步:如果Bob的账户有100元,那么就减去100

5步:然后Smith的账户加上100

6步:释放Bob账户的锁

7步:释放Smith账户的锁

PS:加锁和解锁都是在执行具体语句时会自动做,称为“隐式锁”,上面的操作中把锁抽象出来说了;当然也有“显式锁”,后面具体介绍锁再说。

上述七个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。这也就是事务的原子性。单纯的事务概念并不是故事的全部,下面先简单说一下这一个事务在执行过程中会遇到什么常见问题及解决方案?

1)试想一下,如果执行到第5步时服务器崩溃了,会发生了什么?Bob用户可能会损失100元。那么现实中这种问题肯定是不允许发生的,所以数据库的解决方案就是利用事务日志,当服务器重新启动后数据库启动时会从事务日志中检查到这个事务没有完成,然后数据库进程会把这个事务进行回滚操作,也就是把此事务中执行过的操作反向执行一遍。当所有数据回滚操作都完成之后就会正常提供服务。是不允许用户看到一个中间状态就是100凭空消失了。

2)如果在执行第4步和第5步之间时,另外一个进程要删除Bob的所有余额(比喻刷卡刷了),那么结果可能就是银行在不知道这个逻辑的情况下白白给Smith用户100元。但这里想说的是,由于使用了锁机制(MySQL有表锁和行锁)所以不会出现这种情况。如下图:

MySQL InnoDB单机事务原理

由于第一个事务操作时使用了锁,所以第二个事务以及第三个事务在第一个事务操作时都无法操作Bob账户和Smith账户。直至事务1整个事务完成。另外还有一个特殊的情况就是当事务1执行第4步后此时这两个账户都没有这100块钱,而这个中间状态同样是不允许被其他进程看见的,这也就是保证数据的一致性。

3)如果当执行到第3步时发现Bob账户上并没有100元,这属于业务属性不匹配,此时这个事务是不是就不应该再执行了,应该马上利用事务日志进行事务回滚操作。

不一样的事务?

除了上面给出的这个事务单元的例子外,还有一些操作也是事务单元。InnoDB存储引擎是一个事务型存储引擎,所以在InnoDB存储引擎上所有的针对数据的操作都是一个事务单元。比如,对这个表建立索引或删除索引是一个事务单元、从这个表读取数据也是一个事务单元、向表中写入一行记录并同时更新这行记录的所有索引也是一个事务单元、删除整张表也是一个事务单元,不过InnoDB默认是自动提交事务(这个后面会说)。而这些操作都会记录到事务日志中已保证数据的安全性,比如你删除一张大表,正在执行删除时机器宕机了,那么当你重新启动数据库时会根据事务日志检测到事务没有完成,所以会回滚已经删除了的数据,回滚完成后数据库服务才能正常提供服务。从MySQL 8.0开始,对于DDL操作才已经支持原子性了,在之前版本是不支持的。

结合上述问题,一个真正支持事务的关系型数据库系统必须要经过严格的ACID测试,ACID标准能够保证事务的完整性要求,否则空谈事务的概念是不够的。 

三、ACID

一个支持事务的数据库必须要支持 ACID 特性,也就是事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。这四点对于一个支持事务的数据库非常重要,但是实际上要真正做到 ACID 也是非常难的。在 InnoDB 存储引擎中的事务是完全符合 ACID 的特性,下面介绍一下 ACID

  • 原子性(Atomicity

原子性指的是一个事务中的所有操作,要么全部成功(提交)、要么全部失败(回滚),不能出现部分成功部分失败。

对 InnoDB 来说,只要客户端收到服务端发送过来的 commit 成功报文,那么这个事务一定是成功的。如果收到的是 rollback 的成功报文,那么整个事务的所有操作一定都要被回滚掉,就好像什么都没执行过一样。另外,如果连接中途断开或者服务端异常,事务也要保证会滚掉。

从内部实现的角度看,InnoDB 对事务过程中的数据变更总是维持了 undo log,若用户想要回滚事务,能够通过 undo 追溯最老版本的方式,将数据全部回滚回来。若用户需要提交事务,则将提交日志刷到磁盘。但是原子性并不能确保事务的过程也是原子发生的,约束事务过程的是隔离性。

  • 一致性(Consistency

一致性,指的是在任何时刻,包括数据库正常提供服务的时候,数据库从异常中恢复过来的时候,数据都是一致的,包括内部数据存储的准确性,数据结构(例如 btree)不被破坏,保证不会读到中间状态的数据。在 InnoDB 中,主要通过 crash recovery 和 double write buffer 的机制保证数据的一致性。前者保证恢复时能够将所有的变更应用到数据页上,后者保证数据页的准确性。如果崩溃恢复时存在还未提交的事务,那么根据 XA 规则提交或者回滚事务,最终实例总能处于一致的状态。

另外一种一致性指的是数据之间的约束不应该被事务所改变,例如外键约束。MySQL 支持自动检查外键约束,或是做级联操作来保证数据完整性,但另外也提供了选项 foreign_key_checks,如果你开启了这个选项,数据间的约束和一致性就会失效。有些情况下,数据的一致性还需要用户的业务逻辑来保证。

  • 隔离性(Isolation

隔离性就是以性能为缘由,对一致性的一种破坏。为什么这么说呢?上面我们说了必须要保证事务的一致性,保证事务不会读到中间状态的数据,那么保证一致性最好的实现方式就是让事务一个一个按照顺序串行执行,而事务间的隔离性就违背事务的一致性,隔离级别中的 RU RC 级别都破坏了数据的一致性。但由于事务串行执行的效率太低才有了隔离性,好处就是通过不同的机制来提高事务的执行效率。

后面介绍隔离级别时在详解。隔离性还有一些其他称呼,如并发控制、可串行化和锁。通常来说,一个事务所做的修改在最终提交之前对其他事务是不可见的,即一个事务的执行不会影响另外一个事务的执行,这通常使用锁来实现。后面讨论隔离级别时,会发现为什么要说“通常来说”是不会可见的。在 InnoDB 存储引擎中隔离性就是靠锁来保证的。

  • 持久性(Durability

当一个事务一旦提交,其结果就是永久性的保持在数据库中,即使在事务数据的变更还没有刷新到磁盘的时候发生服务器宕机行为,数据库也能将数据恢复。在内部实现中,InnoDB 通过 redo 保证已经提交的数据一定不会丢失。

这个特性除了和数据库系统相关外,还和你的硬件条件相关。 InnoDB 给出了许多选项,你可以为了追求性能而弱化持久性,也可以为了完全的持久性而弱化性能。需要注意的是,持久性只能从事务本身的角度来保证结果的永久性,如事务提交后,所有的变化都是永久的。但如果不是数据库本身的故障而是一些外部的原因,比如磁盘坏掉了,那么所有数据库信息都会丢失。因此持久性保证的是事务系统的高可靠性,而不是高可用性,对于高可用性的实现事务本身并不能保证。

Note

这里的一致性与分布式数据库的一致性是不一样的,两者没有关系。分布式数据库的一致性是定义了读操作与写操作之间的关系,有强一致性和弱一致性之分。强一致性,或者称为线性一致性,Linearizability,也就是 CAP 中的 C,它要求,每一个读操作都将返回『最近的写操作』(基于单一的实际时间)的值。弱一致性则放宽了这种限制。

四、多事务单元

根据上面介绍过的单事务单元后,下面说一说多事务单元时间的关系,如下图:

MySQL InnoDB单机事务原理

事务单元①会锁定BobSmith这两个账号,同一时刻事务单元②会请求锁定Smith(但由于被事务单元①锁定,所以只能等待)和job这两个账号,同一时刻事务单元③会请求锁定joeBob这两个账号(但由于两个账号都被锁定,所以只能等待)。

从以上说明可以看出,当多个事务针对同一个数据时会排队执行,也就是说只有当前面一个事务完成之后,后面的一个事务才能够去执行操作,它只能看见前一个事务执行完成后的记过而看不到中间状态。有人分析过事务单元跟事务单元之间可能发生的关系(事务处理这本书中讲的),如是有了以下答案,也称之为事务单元之间的happen-before关系,主要由以下四种关系(这四种关系指的是多个事务之间对同一个数据的操作,一定要清楚这个不然会搞混)。

MySQL InnoDB单机事务原理

那么问题来了,既然事务跟事务之间只有这四种关系,接下来就是考虑如何能够以最快的速度完成?但前提是必须要保证事务单元之间的一致性关系之后(保证上面四种操作的逻辑顺序),仍然可以让系统能够以最快的速度完成。把这两个因素进行细致化总结之后,我们会发现它有这么几种不一样的做法。

第一选择:排队法

MySQL InnoDB单机事务原理

第二选择:排他锁

MySQL InnoDB单机事务原理

使用排对法来处理事务虽然是一种办法,且保证数据的一致性,但是效率实在太低,如果有大的事务执行时后面的所有事务都必须得等待,直到超时。后来数据库大师们又想到了使用锁(行锁)来处理事务,也就是当两个事务操作的数据不是同一个的时候就可以同时来执行;但是当两个事务操作的数据有相同的时候就无法进行并行执行了。

但是人们对于速度的追求是无禁止的,有没有更好的方法可以并行的速度更快呢?于是又有一些聪明人想到一些聪明的办法。

第三选择:读写锁

MySQL InnoDB单机事务原理

如上图,人们发现事务单元之间的happen-before关系,其中如果把读写锁分开的话,那样写读、写写、读写、读读中的读读就可以完全并行到一起,而对已其他三种仍然让它串行。这样的话你可以发现对于那种读多写少环境一定可以进一步提升事务的并行度。这一做法也是以前主流数据库的一些做法,用读写锁加大读的并行度。也就因为这样所以才会出现了隔离性的其中两个不同的隔离级别,一个是“可重复读”,一个是“读已提交”。

“可重复读”就是在同一个事务中发出同一个SELECT语句两次或更多次,那么产生的结果数据集总是相同的。因此,使用可重复读隔离级别的事务可以多次检索同一行集,并对它们执行任意操作,直到提交或回滚操作终止该事务,但可重复读只能解决读读的并行执行,并不能使读写、写写、写读的并行执行,下图解释的很详细。

MySQL InnoDB单机事务原理

这个时候如何能让读写做到并行呢?又提出一种隔离级别就是“读已提交”。在读已提交隔离级别,他们用的方法就是“读锁升级”,也就是说此时可以做到“读读”并行时且如果后来又来了一个写锁,可以把读进行升级,也就是允许读的时候进行写操作。在这个情况下的时候你会发现这个系统就可以进一步的提高并行度(如下图)。

MySQL InnoDB单机事务原理

但是这种模式所带来的代价就是在一个事务中,第一次读的结果是一个数据,而第二次读的结果又是另外一个数据了。这个问题也就是所谓的“不可重复读”问题。同时你也更能感受到了ACIDI就是对C的破坏了。具体就是当“读读”两个事务并行的时候,这时又有一个事务进行写操作,于是就会出现一个问题,可能原先那个事务版本号为0的记录这个时候由于更改已经变为1了。由于都是并行执行,所以当第一个读事务再次有读请求进来就会发现数据被更改了,两次读取的数据的版本是不同的且数据也不同。

第四选择:MVCC(多版本并发控制)

看完上面三种选择之后发现从一开始的事务一条一条执行,到针对不同数据操作时事务的并行执行,到读读并行执行,到读写并行执行。这些以加锁的方式来增加并行执行已经是很老的方法了。

接下来介绍目前数据库厂商主流的一种事务并行控制方式MVCC机制,看完之后只有无限的膜拜大神。MVCC可以做到写不阻塞读,主要针对写进行的优化(上面的都是针对读进行的优化),解决了维持一个大事务的问题,在没有MVCC之前如果有一个大的写操作事务在运行,那么其余的读只能等待,极大降低了并发性能;使用MVCC的代价就是系统复杂度变大(后面会介绍MVCC)。

MySQL InnoDB单机事务原理

通过上面对多事务执行的一系列分析,大概说明了事务并发整个发展过程以及事务并发执行会产生哪些问题。首先多事务并行必面临读读、读写、写读和写写这四个问题,那么如何让这四种操作能够并行执行也就是ACID中的隔离性所要考虑的问题。InnoDB对于ACID中的隔离性使用InnoDB锁和MVCC机制来控制管理,而对于原子性、一致性和持久性则是靠redoundo这两个日志实现的。事务的ACID特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难,甚至可以说是不可能完成的任务。一个兼容ACID的数据库系统,需要做很多复杂的工作,才能确保ACID的实现。

五、事务日志

上面多次提到事务日志(redo),以及用于事务回滚的undo回滚日志。事务日志用于保证事务的可靠性,在进行事务操作时先将操作写进事务日志,而事务日志会记录老数据和新数据从而实现回滚。并且不用每次都将修改的数据本身持久到磁盘,事务日志采用的是追加的方式记录。因此写日志操作时磁盘上一小块区域内的顺序I/O,而不像随机I/O需要在磁盘的多个地方移动磁头。所以采用事务日志的方式相对来说要快得多。事务日志持久以后,内存中被修改的数据在后台可以慢慢地刷回磁盘。目前大多数存储引擎都是这样设计的。我们通常称之为预写式日志,修改数据需要写两次磁盘。

如果数据的修改意见记录到事务日志并持久化,但数据本身还没有写回磁盘,此时系统崩溃,存储引擎在重启时能够自动恢复这部分修改的数据。具体的恢复方式视存储引擎而定。


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (3)
[资助本站您就扫码 谢谢]
分享 (0)

您必须 登录 才能发表评论!