在经典的数据库理论里,本地事务具备四大特征:

  • 原子性
  • 事务中的所有操作都是以原子的方式执行的,要么全部成功,要么全部失败;

  • 一致性
  • 事务执行前后,所有的数据都应该处于一致性状态---即要满足数据库表的一致性约束,也要达到业务一致性(完成了业务目标);

  • 隔离性
  • 并发执行的事务之间不应该互相干扰;隔离性的强度由隔离级别决定;

  • 持久性
  • 事务一旦被提交,它添加/修改的数据不会随着宕机/重启而丢失;

在MySQL(InnoDB引擎)中,原子性和持久性是通过Redo Log来实现的,一致性是通过Undo Log实现的,而隔离性则是通过锁和MVCC来实现的。

ARIES算法

如果需要深入了解数据库本地事务原理,不得不提到ARIES算法,该算法全称为Algorithms for Recovery and Isolation Exploiting Semantics(基于语义的恢复与隔离算法),众多主流的关系型数据库都受到该算法的影响。

ARIES算法主要针对使用No Force + Steal的数据写入策略而采用的一种数据恢复方式。

该算法主要基于三个主要的原则:

  • Write-ahead logging
  • 出于性能上的考虑,数据的修改都是在内存中进行,并将数据修改操作记录到日志(Redo Log和Undo Log)中,然后异步将内存中的数据写入到磁盘;

  • 通过Redo Log恢复数据
  • Redo Log用于记录事务对数据的修改操作,在数据库崩溃恢复时,ARIES通过Redo Log重放那些还未写入到数据库磁盘中的数据操作,将数据恢复至崩溃前的状态;

  • 通过Undo Log回滚数据
  • 对崩溃前未提交的事务,通过Undo Log进行回滚;

Write-ahead logging

每个事务执行时,都是在内存中进行数据的修改,并将这些“修改操作”记录到日志,然后将内存中的数据异步写入到磁盘里;

但日志也并非立刻写入至磁盘,而是先写入到Log Buffer,再按照相应的参数配置进行磁盘的写入操作;在写入至磁盘时,数据会先写入至操作系统内核缓冲区(OS Buffer),然后根据参数配置决定对内核缓冲区中的数据同步或异步刷盘。

如在InnoDB中,Redo Log的磁盘写入策略是由innodb_flush_log_at_trx_commit参数值来决定的:

0: 当参数值设置为0时,每隔1秒将Redo Log Buffer中的数据写入至OS Buffer,并同时调用fsync()函数完成刷盘操作;

1: 每次事务提交时,立即将Redo Log Buffer中的数据写入至OS Buffer,并同时调用fsync()函数完成刷盘操作;

2: 每次事务提交时,立即将Redo Log Buffer中的数据写入至OS Buffer,每隔1秒调用fsync()函数完成刷盘操作;

由此可见,当innodb_flush_log_at_trx_commit设置为0或2时,都会导致日志数据丢失;

以上讨论了“数据操作”日志的写入方式,而对于事务中真正修改的数据,Write-ahead logging根据事务提交的时间节点,将变动的数据写入至磁盘的时间节点分为Force和Steal两种:

Force:在事务提交时,是否强制将变动的数据完全写入至磁盘?

Steal:在事务提交前,是否允许将变动的数据提前写入至磁盘?

因此根据Force和Steal的值,数据的写入策略可以分为以下四种:

  Steal No Steal
Force

事务提交时,强制将变动数据完全写入至磁盘

事务提交前,允许将变动的数据提前写入至磁盘

事务提交时,强制将变动数据完全写入至磁盘

事务提交前,不允许变动的数据提前写入至磁盘

No Force

事务提交时,不需要强制变动数据完全写入至磁盘

事务提交前,允许将变动的数据提前写入至磁盘

事务提交时,不需要强制变动数据完全写入至磁盘

事务提交前,不允许变动的数据提前写入至磁盘

直观感觉就可以知道,采用No Force + Steal的方式,不需要强制在事务提交时,所有的数据改动都必须写入至磁盘,同时允许变动的数据在事务提交之前即可提早写入至磁盘;这样的写入策略灵活性强且性能最好;MySQL InnoDB采用的就是此种写入方式。

Redo Log

Physiological Logging

在崩溃并重启后,数据库重放Redo Log进行数据恢复时,由于并不知道崩溃前哪些变动的数据已经写入到物理磁盘,因此需要保证Redo Log的重放是幂等的,即多次重放得到的结果不会改变;

InnoDB中所有的数据都是以数据页(Page)的形式存在于磁盘中的,因此Redo Log中的每一条日志,会记录被修改的数据页Page ID、被修改的记录在该Page中的位移、记录中哪些字段被修改了、修改后的字段值:

(Page ID,Record Offset,(Filed 1, Value 1) … (Filed i, Value i) … )

一个事务可能修改多条记录(这些记录可能位于同一个数据页,也可能位于不同的数据页),就会产生多条日志;同时数据库的多个事务都是并行执行的,出于性能的考虑,它们在Redo Log中并非以串行的方式写入,而是多个事务产生的多条日志互相穿插在Redo Log中,这就导致了Mini Transaction(Mtr)的产生。Mtr是数据库事务在Redo Log中的最小存储单元,一个数据库事务被划分为一个或多个Mtr,一个Mtr仅包含对一个数据页的修改(由于一个数据页可能包含多条记录,因此一个Mtr中包含的日志记录也不止一条)。

虽然同一个事务的多个Mtr在Redo Log中可能是不连续的,但同一个Mtr中包含的多条日志在Redo Log中一定是连续的。

我们把Redo Log这样的的存储方式称之为Physiological Logging。

LSN机制

Redo Log不是一个无限膨胀的日志文件,它具有固定的长度,日志先按照物理顺序一直往后添加,当达到空间限制后跳转到开始位置重新进行写操作/覆盖。

Redo Log中的记录也并非永远具有存在的价值,当事务所操作的数据已经被写入到物理磁盘中,这个事务对应的日志就没有存在的意义,事实上是可以被删除了。

MySQL数据库使用CheckPoint的值来指定日志文件可以擦除的位置,也就是说该位置之前的日志都是可以删除的;CheckPoint的值用LSN来表示。

LSN(Log Sequence Number)并非物理位置,它是一个8字节(64位)的整数且单调递增,代表着自数据库启动以来,至当前时间点写入至Redo Log中数据的总量(字节数)。

Redo Log中的每一条日志都是用LSN作为它的标识。

Double Write Buffer

上文谈到数据库通过异步的方式将修改的数据写入至物理磁盘,但如果无法保证数据写入到物理磁盘的原子性,当恰好写入了部分数据后发生崩溃,这会导致物理磁盘中存在一个被损坏的数据页;而Redo Log只记录哪些数据页被修改,但不会记录哪些数据页被损坏,因此无法通过Redo Log来修复这些被损坏的数据页;

MySQL InnoDB使用Double Write Buffer来解决写数据至物理磁盘时崩溃后数据页的恢复问题;

Double Write Buffer是一个存储区,当InnoDB尝试写数据至物理磁盘之前,会先将数据写入至该区域;如果写数据时崩溃,恢复时InnoDB可以从Double Write Buffer中找到这个被损坏的数据页的副本。

正如名称Double Write所示,这会导致两次写磁盘操作:一次是写入到Double Write Buffer,另外一次是写入到真正的数据页所在的磁盘位置。

另外一个问题:如果日志从Redo Log Buffer写入至磁盘时,数据库崩溃了该如何处理?

Redo Log写入至磁盘是通过日志页(Log Block)的方式进行写入的,日志页不同于内存中的数据页(1 Page = 16 KB),一个日志页的大小为512 Byte。每个日志页中包含该页的摘要值(CheckSum),通过该摘要值可判断写入至磁盘的这个日志页是否完整。

当innodb_flush_log_at_trx_commit设置为2时,事务的提交是以日志写入磁盘作为结束标志的,如果写入时崩溃,则代表事务提交失败,该日志页实际上可以直接丢弃。

Undo Log

在上文Redo Log的Physiological Logging中谈到,Redo Log只会记录某个数据字段修改后的值,在数据库崩溃后恢复阶段会利用Redo Log对事务中的数据操作进行重放,其中包括已提交的事务和未提交的事务;对于那些未被提交的事务,需要使用Undo Log对其进行回滚操作;

与Redo Log使用的Physiological Logging格式不同,Undo Log保存的是逻辑日志;如果事务执行的是一个INSERT操作,Undo Log会保存一条DELETE操作;如果事务中执行的是一个DELETE操作,Undo Log会保存一条INSERT操作;如果事务执行的是一条UPDATE操作,Undo Log中会保存一条反向的UPDATE操作......

Crash Recovery(崩溃恢复)

CheckPoint机制

InnoDB使用了一个叫做Fuzzy Checkpointing的CheckPoint机制来实现数据页写入至磁盘;它并非一次性的将所有内存中的数据页全部写入到磁盘中,因为这会阻塞在写入时的其他数据库操作,因此它采用小批量(Small batches)写入。

当数据库崩溃时,并非所有Redo Log中的事务(包括已提交和未提交的)所修改的数据页都已经写入到了物理磁盘中,我们把那些还未写入的数据页叫做脏页(Dirty Pages)。

在崩溃恢复时,需要找到所有的这些脏页,并利用Redo Log进行重放,也需要找出所有未提交的事务,利用Undo Log进行回滚。

由于Fuzzy Checkpointing只是小批量写入,并非所有已提交事务的数据页都写入至磁盘中;同时由于多个事务(包括已提交和未提交的)会修改同一个数据页,这会导致在写入时可能将未提交事务的数据也写入到磁盘中了;所以在每一次Fuzzy Checkpointing之后,会把该次Fuzzy Checkpointing时未提交的事务列表和脏页列表形成为一个CheckPoint日志,保存到Redo Log中。

在崩溃恢复过程中,InnoDB引擎会找到Redo Log中最近一次CheckPoint日志,获取到未提交的事务列表和脏页列表,并以该日志为起点遍历至Redo Log末尾;在遍历过程中,如果遇到事务提交,将其从未提交事务列表中移除,如果遇到新事务开始,将它加入到未提交事务列表;同时对遍历到的所有Physiological Log,都添加到脏页列表;最后会形成一个最终的未提交事务列表和脏页列表。

对脏页列表,在Redo Log中找到最早的那个脏页所对应的日志,并以此为起点进行Redo Log重放。此时可能会遇到的Redo Log对应的数据页实际已经写入至磁盘中了,不过即使再次重放也没有关系,因为Redo Log是幂等的。

对所有未提交的事务列表,找到其对应的Undo Log,并进行回滚操作。

MySQL数据库本地事务原理的更多相关文章

  1. MySQL数据库的事务管理

    当前在开发ERP系统,使用到的数据库为Mysql.下面介绍下如何开启事务,以及事务隔离的机制 : 1. 检查当前数据库使用的存储引擎. show engines; 2. 修改前my.ini中的文件如下 ...

  2. django中使用mysql数据库的事务

    django中怎么使用mysql数据库的事务   Mysql数据库事务: 在进行后端业务开始操作修改数据库时,可能会涉及到多张表的数据修改,对这些数据的修改应该是一个整体事务,即要么一起成功,要么一起 ...

  3. [MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

    [MySQL数据库之事务.读现象.数据库锁机制.多版本控制MVCC.事务隔离机制] 事务 1.什么是事务: 事务(Transaction),顾名思义就是要做的或所做的事情,数据库事务指的则是作为单个逻 ...

  4. MySQL数据库分布式事务XA优缺点与改进方案

    1 MySQL 外部XA分析 1.1 作用分析 MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,例如开源的代理工具:ameoba[4],网易的DDB,淘宝的 ...

  5. DTP模型之一:(XA协议之三)MySQL数据库分布式事务XA优缺点与改进方案

    1 MySQL 外部XA分析 1.1 作用分析 MySQL数据库外部XA可以用在分布式数据库代理层,实现对MySQL数据库的分布式事务支持,例如开源的代理工具:ameoba[4],网易的DDB,淘宝的 ...

  6. 五分钟详解MySQL并发控制及事务原理

    在如今互联网业务中使用范围最广的数据库无疑还是关系型数据库MySQL,之所以用"还是"这个词,是因为最近几年国内数据库领域也取得了一些长足进步,例如以TIDB.OceanBase等 ...

  7. MySQL的本地事务、全局事务、分布式事务

    本地事务 事务特性:ACID,其中C一致性是目的,AID是手段. 实现隔离性 写锁:数据加了写锁,其他事务不能写也不能读. 读锁:数据加了读锁,其他事务不能加写锁可以加读锁,可以允许自己升级为写锁. ...

  8. 使用Memcache缓存mysql数据库操作的原理和缓存过程浅析

    转载自脚本之家 http://www.jb51.net/article/51831.htm  作者:忙碌的松鼠 对于大型网站如facebook,ebay等网站,如果没有Memcache做为中间缓存层, ...

  9. 记录一次Spring boot 搭建框架连接Mysql数据库注解事务不回滚的故障

    搭建了一个新框架,使用了spring boot 替换以简化原来繁杂的spring配置,使用Spring注解管理事务,持久层使用mybatis. 连接mysql数据库完成项目的过程中发现不支持事务,因为 ...

随机推荐

  1. ARTS Week 18

    Algorithm 本周的 LeetCode 题目为 55. 跳跃游戏 给定一个非负整数数组 nums, 你最初位于数组的 第一个下标 .数组中的每个元素代表你在该位置可以跳跃的最大长度.判断你是否能 ...

  2. Codeforces 777C:Alyona and Spreadsheet(预处理)

    During the lesson small girl Alyona works with one famous spreadsheet computer program and learns ho ...

  3. golang切片的一些自问自答

    你好,我是轩脉刃.这篇是关于go切片的一些问题和回答. go的切片基本上是代码中使用最多的一种数据结构了,使用这种数据结构有哪些要注意的点,这个是非常必要了解的东西.基本上,以前写的一篇博客 http ...

  4. Morphological Image Processing

    目录 概 reflection and translation Erosion and Dilation Erosion 示例 skimage.morphology.erosion dilation ...

  5. Proximal Algorithms 1 介绍

    目录 定义 解释 图形解释 梯度解释 一个简单的例子 Proximal Algorithms 定义 令\(f: \mathrm{R}^n \rightarrow \mathrm{R} \cup \{+ ...

  6. 使用.NET 6开发TodoList应用(12)——实现ActionFilter

    系列导航及源代码 使用.NET 6开发TodoList应用文章索引 需求 Filter在.NET Web API项目开发中也是很重要的一个概念,它运行在执行MVC响应的Pipeline中执行,允许我们 ...

  7. vue打包之后动态修改请求接口方法

    1.可以根据自身情况封装获取配置文件接口信息 1.1我在static中新建一个config.json配置文件 { "DEV_URL":"/apis",//开发模 ...

  8. C++模拟python风格的print函数--打印vector,map,list等结构

    // 最基本实现 template<typename T> static void print(T t) { std::cout << t; } // 处理 std::pair ...

  9. 揭开“QUIC”的神秘面纱

    作者:赵咏 QUIC的发音类似于Quick,实际上也确实很快.它可以很好地解决应用在传输层和应用层面临的各种需求,包括处理更多的连接.安全性以及低延迟. 目前在互联网领域,QUIC可以说刮起了新一代互 ...

  10. gRPC创建Java RPC服务

    1.说明 本文介绍使用gRPC创建Java版本的RPC服务, 包括通过.proto文件生成Java代码的方法, 以及服务端和客户端代码使用示例. 2.创建生成代码工程 创建Maven工程,grpc-c ...