聊一聊 MySQL 中的事务及其实现原理

时间:2021-12-13 03:55:56

原文: 聊一聊 MySQL 中的事务及其实现原理

说到数据库,那就一定会聊到事务,事务也是面试中常问的问题,我们先来一个面试场景:

面试官:"事务的四大特性是什么?"
我:"ACID,即原子性(Atomicity)、隔离性(Isolation)、持久性(Durability)、一致性(Consistency)!"
面试官:"在 MySQL 数据库的 InnoDB 引擎是怎么实现这四大特性的?"
我:"这个...这个....,还真没有了解过哎"
面试官:"那我们就先这个吧,先回去吧,我们会通知你的~"

这可能是比较常见的面试场景了,你也许回答到了事务的四大特性,但是不一定知道他的实现原理。今天我们就来一起打卡事务的四大特性和实现原理,对于原理的实现,这篇文章只是粗略的介绍一下,更多的细节可以关注我后续的文章。

数据库的事务有四大特性:原子性、隔离性、永久性、一致性,下面将介绍这四大特性的定义和在 InnoDB 引擎中是怎么实现的。

原子性

定义

一次操作是不可分割的,要么全部成功,要么全部失败。比如我们的转账操作,不允许出款方成功,收款方失败这种情况,要么都成功,要么多失败,不可能出现中间状态。

实现

InnoDB 引擎使用 undo log(归滚日志)来保证原子性操作,你对数据库的每一条数据的改动(INSERT、DELETE、UPDATE)都会被记录到 undo log 中,比如以下这些操作:

  • 你插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。
  • 你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。
  • 你修改了一条记录,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。

当事务执行失败或者调用了 rollback 方法时,就会触发回滚事件,利用 undo log 中记录将数据回滚到修改之前的样子。

更多关于 undo log 的信息,后面再单独开一篇文章打卡。

隔离性

定义

多个事务并发执行的时候,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

实现

隔离性可能会引入脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)等问题,为了解决这些问题就引入了“隔离级别”的概念。

SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)

  • 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
  • 读提交:一个事务提交之后,它做的变更才会被其他事务看到。
  • 可重复读: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
  • 串行化: 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下:

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读提交 不可能 可能 可能
可重复读 不可能 不可能 可能
串行化 不可能 不可能 不可能

上面就是几种隔离级别可能出现的并发问题,但是有必要说一下,你隔离得越严实,效率就会越低。

InnoDB 引擎是如何保证隔离性的?利用锁和 MVCC 机制。这里简单的介绍一下 MVCC 机制,也叫多版本并发控制,在使用 READ COMMITTD、REPEATABLE READ 这两种隔离级别的事务下,每条记录在更新的时候都会同时记录一条回滚操作,就会形成一个版本链,在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。

持久性

定义

事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

实现

要保证持久性很简单,就是每次事务提交的时候,都将数据刷磁盘上,这样一定保证了安全性,但是要知道如果每次事务提交都将数据写入到磁盘的话,频繁的 IO 操作,成本太高,数据库的性能极低,所以这种方式不可取。

InnoDB 引擎是怎么解决的?InnoDB 引擎引入了一个中间层来解决这个持久性的问题,我们把这个叫做 redo log(归档日子)

为什么要引入 redo log?redo log 可以保证持久化又可以保证数据库的性能,相比于直接刷盘,redo log 有以下两个优势:

  • redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
  • redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。

InnoDB 引擎是怎么做的?当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里面,并更新内存,这个时候更新就算完成了。当数据库宕机重启的时候,会将 redo log 中的内容恢复到数据库中,再根据 undo log和 binlog 内容决定回滚数据还是提交数据。

更多 redo log,后面我打算单独写一篇文章。

一致性

定义

一致性简单一点说就是数据执行前后都要处于一种合法的状态,比如身份证号不能重复,性别只能是男或者女,高考的分数只能在0~750之间,红绿灯只有3种颜色,房价不能为负的等等, 只有符合这些约束的数据才是有效的,比如有个小孩儿跟你说他高考考了1000分,你一听就知道他胡扯呢。数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据库世界中有所体现。如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是一致的,或者说符合一致性的。

实现

要保证数据库的数据一致性,要在以下两个方面做努力:

  • 利用数据库的一些特性来保证部分一致性需求:比如声明某个列为NOT NULL 来拒绝NULL值得插入等。
  • 绝大部分还是需要我们程序员在编写业务代码得时候来保证

以上就是我今天要分享的内容,希望这篇文章对你的学习或者工作有所帮助,感谢您的阅读,如果您觉得文章不错欢迎点赞 转发,感谢。

最后

目前互联网上很多大佬都有 MySQL 相关文章,如有雷同,请多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有所错误之处,还望提出,谢谢。

欢迎扫码关注微信公众号:「平头哥的技术博文」,和平头哥一起学习,一起进步。

聊一聊 MySQL 中的事务及其实现原理