MySQL事务实现原理

🌙我深怕自己并非美玉🌙
🌙故而不敢加以刻苦琢磨🌙
🌙却又半信自己是块美玉🌙
🌙故而不肯庸庸碌碌🌙
🌙与瓦砾为伍🌙

MySQL的逻辑架构

MySQL的逻辑架构
如图所示,MySQL服务器的逻辑架构从上到下分为三层:

  • 第一层:连接层,负责处理客户端连接、授权认证等。

  • 第二层:核心服务器层,负责查询语句的解析、优化、缓存以及内置函数的实现、存储过程等。

  • 第三层:存储引擎层,负责MySQL中数据的存储与提取。

    MySQL中服务器层不管理事务,事务是由存储引擎实现的;MySQL支持事务的存储引擎有InnoDB,NDB Cluster等;使用最多的就是InnoDB,也是MySQL默认使用的存储引擎,其他引擎不支持事务,比如MyISAM,Memory等。

MySQL 事务

本文中的设置都是当前连接的设置,重启就会失效,想要持久化,在MySQL的配置文件中设置

基本要素(ACID)

  1. 原子性(Atomicity):事务开始后所有的操作,要么全部执行完,要么都全部不执行,不可能停滞在中间环节;事务执行过程中如果出错,会回滚到事务开始前的状态,所有操作就像没有发生一样;也就是说事务是一个不可分割的整体。

  2. 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。🌰:A向B转账,不可能A扣了钱,B却没收到。

  3. 隔离性(Isolation):同一时间,只允许一个事务请求同一条数据,不同的事务之间彼此没有任何干扰。🌰:A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

  4. 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

-- 查看是否开启自动提交,默认为自动提交
show variables like 'autocommit';
select @@autocommit;

-- 设置手动提交事务
set autocommit = 0;

-- 手动开始事务
-- 1. 开始事务
start transactionbegin transaction-- 2. 写语句
delete ...
-- 设置保存点
savepoint p1;
update ...
-- 设置保存点
savepoint p2;
-- 可选回滚到哪个状态,回滚不是失败
rollback to p1(p2);
-- 3. 提交事务
commit;

原子性(Atomicity)

MySQL日志

在说明原子性原理之前,首先介绍一下MySQL的事务日志,MySQL的日志有很多种,比如二进制日志(binlog)、错误日志、查询日志、慢查询日志;除此之外InnoDB存储引擎还提供了两种事务日志,redo log(重做日志)undo log(回滚日志)其中 redo log 用于保证事务的持久性,而 undo log 则是事务原子性和隔离性实现的基础

实现原理
  • 实现原子性的关键,是当事务回滚时能够撤销所有已经执行成功的SQL语句,InnoDB存储引擎实现回滚,靠的就是undo log,当事务对数据库进行修改时,InnoDB存储引擎会生成对应的 undo log;如果事务执行失败或者调用了 rollback,导致事务需要回滚,InnoDB存储引擎就会利用 undo log 中的信息将数据回滚到修改之前的样子。

  • undo log属于逻辑日志,它记录的是SQL执行相关的信息,当发生回滚时,InndDB存储引擎会根据 undo log 的内容执行与之前相反的操作,对于每个 insert,回滚时会执行 delete,对于每个 update,回滚时会执行一个相反的 update。

    🌰:

    假设有 A,B 两个数据;值分别为 1,2;

    1. 事务开始

    2. 记录 A=1 到 undo log

    3. 修改 A=3

    4. 记录 B=2 到 undo log

    5. 修改 B=4

    6. 将 undo log 写到磁盘

    7. 将数据写到磁盘

    8. 事务提交

    如果挂了,回滚到 A=1,B=2;这就要通过 undo log 了。

一致性(Consistency)

定义

一致性是指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态,即数据库的完整性没有被破坏,事务执行的前后都是合法的状态。

数据库的完整性包括但不限于:行完整性(如:行的主键存在且唯一)、列完整性(如:字段的类型、大小、长度符合要求)、外键约束、用户自定义完整性。

实现原理

可以说,一致性是事务追求的最终目标:原子性、持久性和隔离性,都是为了保证数据库状态的一致性;除此之外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。

措施:

  • 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证
  • 数据库本身提供保障,如:不允许向整形列插入字符串值、字符串长度不能超过列的限制等
  • 应用层面进行保障,如:如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致

隔离性(Isolation)

并发问题
  1. 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。

  2. 不可重复读:事务A多次读取同一条数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取同一数据时,结果不一致。

  3. 幻读:🌰:系统管理员A将数据库中所有学生的成绩从具体分数更新为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A更新结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  • 👀:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
实现原理

隔离性追求的是并发情况下事务之间互不干扰;简单起见,我们主要考虑最简单的读写操作;

  • 一个事务的写操作对另一个事务写操作的影响:锁机制保证隔离性

    锁机制的基本原理概括:

    事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是被锁定的;其他事务如果需要修改数据,只能等待当前事务提交或回滚后释放锁。(共享锁和独占锁)

  • 一个事务的写操作对另一个事务读操作的影响:MVCC保证隔离性

    MVCC 全称 Multi-Version Concurrency Control,即多版本并发控制。

    MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
    多版本控制:

    指的是一种提高并发的技术,最早的数据库系统,只有读读之间可以并发,读写、写读、写写之间都要阻塞;引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅的提高了InnoDB存储引擎的并发度;
    概述:

    MVCC 在 MySQL InnoDB 存储引擎中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,做到非阻塞并发读。

隔离级别

事务的隔离级别分为:未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)、串行化(serializable)。

默认隔离级别:RR(可重复读),RR的实现主要基于锁机制(next-key lock)MVCC(数据的隐藏列、基于undo log 的版本链、ReadView)

四种级别由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题。

❌可能出现 ✔不会出现

隔离级别脏读不可重复读幻读
未提交读(read uncommitted)
已提交读(read committed)
可重复读(repeatable read)
串行化(serializable)

未提交读:A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚;(出现脏数据)

已提交读:A事务执行更新;B事务查询;A事务又执行更新;B事务再次查询时,前后两次数据不一致;(不可重复读)

可重复读:A事务无论执行多少次,只要不提交,B事务查询值都不变;B事务仅查询B事务开始时那一瞬间的数据快照;(幻读)

串行化:不允许读写并发操作,写执行时,读必须等待;

-- 查看事务隔离级别,默认为可重复读(repeatable read)
show variables like 'tx_isolation';
select @@tx_isolation;
-- MySQL 8.0+
show variables like 'transaction_isolation';
select @@transaction_isolation;

-- 设置隔离级别
set session transaction isolation level xxx;
-- 设置全局的隔离级别
set global transaction isolation level xxx;

持久性(Durability)

定义

持久性指事务一旦提交,它对数据库的改变就是永久的,接下来的其他操作不能对其有任何影响,并且不能回滚。

redo log

InnoDB 存储引擎作为 MySQL 的存储引擎,数据要写入磁盘的,但如果每次读写数据都需要磁盘IO,效率会很低;为此,InnoDB 存储引擎提供了缓存池(Buffer Pool),BP 中包含了部分数据页的映射,作为访问数据库的缓冲,当从数据库写入数据时,会首先写入 BP,BP 中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)

BP 的使用大大提高了读写数据的效率,但是也带来了新的问题;如果 MySQL 宕机,而此时 BP 中修改的数据还没有刷新的磁盘,就会导致数据的丢失,事务的持久性无法保证。

实现原理

redo log 的引入就是来解决这个问题(宕机导致 BP 中的数据没有刷新磁盘,造成数据丢失);当数据被修改时,除了修改 BP 中的数据,还会在 redo log 中记录这次操作;当事务提交时,会把此次事务操作信息写入 redo log,如果 MySQL 宕机,重启时可以读取 redo log 中的操作信息,对数据库进行恢复,redo log 采用的是WAL(Write-ahead logging,预写式日志), 所有修改先写入日志,在更新到 BP,保证了数据不会因为 MySQL 宕机而丢失,从而保证了持久性。

🌰:

假设有A、B两个数据,值分别为1、2;

  1. 事务开始
  2. 记录 A=1 到 undo log
  3. 修改 A=3
  4. 记录 A=3 到 redo log
  5. 记录 B=2 到 undo log
  6. 修改 B=4
  7. 记录 B=4 到 redo log
  8. 将 undo log 写到磁盘
  9. 将 redo log 写到磁盘
  10. 事务提交

数据写在缓存池中,但是 redo log 已经刷入磁盘了。

❓❓❓既然 redo log 也需要在事务提交时将日志写入磁盘,为什么它比直接将 BP 中修改的数据写入磁盘(即刷脏)要快呢?

  1. 刷脏是随机IO,因为每次修改的数据位置是随机的,但写 redo log 是追加操作,属于顺序IO。
  2. 刷脏是以数据页(Page)为单位的,MySQL 默认页的大小是16KB,一个 Page 上一个小修改都要整页写入,而 redo log 中只包含真正需要写入的部分,无效IO大大减少。

redo log 与 binlog

我们知道,在MySQL中还存在 binlog(二进制日志) 也可以记录写操作并用于数据的恢复,但二者本质不同:

  1. 作用不同:redo log 是用于 crash recovery 的,保证 MySQL 宕机也不会影响持久性;binlog 是用于 point-in-time recovery 的,保证服务器可以基于时间点恢复数据,除此之外 binlog 还用于主从复制。

  2. 层级不同:redo log 是 InnoDB 存储引擎实现的,而 binlog 是 MySQL 的服务器层。

  3. 内容不同:redo log 是物理日志,内容基于磁盘的 Page;binlog的内容是二进制的,根据 binlog_format 属性值的不同,可能基于SQL语句、基于数据本身或者基于二者的混合。

  4. 写入时机不同:binlog 在事务提交时写入;redo log 的写入时机相对多元:

  • 前面曾提到:当事务提交时,会对 redo log 进行刷盘;这是默认情况下的策略,修改 innodb_flush_log_at_trx_commit 属性值可以改变这个策略,但事务的持久性将无法保证。
  • 除了事务提交时,还有其他刷盘时机:如 master thread 每秒刷盘一次 redo log 等,这样的好处是不一定要等到 commit 时刷盘,commit 速度大大加快。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

月牙坠

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

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

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

打赏作者

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

抵扣说明:

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

余额充值