2019独角兽企业重金招聘Python工程师标准>>>

1、前言

狼哥一直对数据库Mysql这块没有进行过系统的深入学习,今天看到一篇文章写的还不错,特意分享一下,我不能保证文章中所讲的都是正确,大家都应该带着探索的思维去阅读别人的文章。

数据库的4大事务特性ACID,想必大家都应该知道。

ACID表示原子性、一致性、隔离性和持久性。一个很好的事务处理系统,必须具备这些标准特性:

  • 原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。

  • 一致性(consistency):数据库总是从一个一致性的状态转换到另一个一致性的状态。(其实原子性和隔离性间接的保证了一致性)

  • 隔离性(isolation):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。

  • 持久性(durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。

而我们最常说的隔离性其实有对应的隔离级别,MySQL规定的隔离级别有4种,分别是:

  • READ UNCOMMITTED(读未提交):在此级别里,事务的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,也就是会产生脏读,在实际应用中一般很少使用。

  • READ COMMITTED(读已提交):大多数数据库系统的默认隔离级别都是它,但是MySQL不是。它能够避免脏读问题,但是在一个事务里对同一条数据的多次查询可能会得到不同的结果,也就是会产生不可重复读问题。

  • REPEATABLE READ(可重复读):该隔离级别是MySQL默认的隔离级别,看名字就知道它能够防止不可重复读问题,但是在一个事务里对一段数据的多次读取可能会导致不同的结果,也就是会有幻读的问题(注:这里说的无法解决是MySQL定义层面,对于InnoDB引擎则完美的解决了幻读的问题,如果你正在使用InnoDB引擎,可忽略)

  • SERIALIZABLE(可串行化):该隔离级别是级别最高的,它通过锁来强制事务串行执行,避免了前面说的所有问题。在高并发下,可能导致大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,因为RR级别解决了所有问题。

可以看到隔离级别里最重要的只有两个隔离级别:RC和RR。那么问题来了,我们知道上面说的ACID以及隔离级别的实现原理吗?无论是平时工作还是面试,这部分的问题都重中之重,接下来,我会抛出几个问题,大家可以带着问题来看此文:

ACID问题:

  • InnoDB能够保证原子性?用的什么方式?
  • 为什么InnoDB能够保证一致性?用的什么方式?
  • 为什么InnoDB能够保证持久性?用的什么方式?

隔离性里隔离级别的问题:

为什么RU级别会发生脏读,而其他的隔离级别能够避免? 为什么RC级别不能重复读,而RR级别能够避免? 为什么InnoDB的RR级别能够防止幻读? 解决这些问题之前,我们要首先知道Redo log、Undo log以及MVCC都是什么。

2、Redo log

redo log(重做日志)用来实现事务的持久性,即事务ACID中的D。其由两部分组成,一是内存中的重做日志缓冲(redo log buffer),其实易失的。二是重做日志文件(redo log file),其是持久的。

在一个事务中的每一次SQL操作之后都会写入一个redo log到buffer中,在最后COMMIT的时候,必须先将该事务的所有日志写入到redo log file进行持久化(这里的写入是顺序写的),待事务的COMMIT操作完成才算完成。

由于重做日志文件打开没有使用O_DIRECT选项,因此重做日志缓冲先写入文件系统缓存。为了确保重做日志写入磁盘,必须进行一次fsync操作。由于fsync的效率取决于磁盘的性能,因此磁盘的性能决定了事务提交的性能,也就是数据库的性能。由此我们可以得出在进行批量操作的时候,不要for循环里面嵌套事务。

参数 innodb_flush_log_at_trx_commit 用来控制重做日志刷新到磁盘的策略,该参数有3个值:0、1和2。

  • 0:表示事务提交时不进行写redo log file的操作,这个操作仅在master thread中完成(master thread每隔1秒进行一次fsync操作)。

  • 1:默认值,表示每次事务提交时进行写redo log file的操作。

  • 2:表示事务提交时将redo log写入文件,不过仅写入文件系统的缓存中,不进行fsync操作。

我们可以看到0和2的设置都比1的效率要高,但是破坏了数据库的ACID特性,不建议使用!

对比binlog

在MySQL数据库中还有一种二进制日志(binlog),从表面上来看它和redo log很相似,都是记录了对数据库操作的日志,但是,它们有着非常大的不同。

首先,redo log是在MySQL的InnoDB引擎层产生,而binlog则是在MySQL的上层产生,它不仅针对InnoDB引擎,其他任何引擎对于数据库的更改都会产生binlog。

其次,两种日志记录的内容形式不同,binlog是一种逻辑日志,其记录的是对应的SQL语句。而redo log则是记录的物理格式日志,其记录的是对于每个页的修改。

此外,两种日志记录写入磁盘的时间点不同,binlog只在事务提交完成后一次性写入,而redo log在上面也说了是在事务进行中不断被写入,这表现为日志并不是随事务提交的顺序进行写入的。

redo log block

在InnoDB引擎中,redo log都是以512字节进行存储的(和磁盘扇区的大小一样,因此redo log写入可以保证原子性,不需要double write),也就是重做日志缓存和文件都是以块的方式进行保存的,称为redo log block,每个block占512字节。

重做日志除了日志本身之外,还由日志块头(log block header)及日志块尾(log block tailer)两部分组成。

下面我来解释一下组成Log Block header的4个部分各自的含义: LOGBLOCKHDR_NO:它主要用来标记所处Redo Log Buffer中Log Block的位置。 LOGBLOCKHDRDATALEN:它表示Log Block所占用的大小。当Log Block被写满时,该值为0x200,表示使用全部Log Block空间,即占用512字节。 LOGBLOCKFIRSTRECGROUP:表示Log Block中第一个日志所在的偏移量,如果该值大小和LOGBLOCKHDRDATALEN相同,则表示当前Log Block不包含新的日志,如果事务的日志大小超过一个Log Block的大小,剩余的将会接着保存到一个新的Log Block中。 LOGBLOCKCHECKPOINT_NO:表示该Log Block最后被写入时的检查点第4字节的值。 Log Block tailer只包含一个LOGBLOCKTRLNO,它的值和LOGBLOCKHDRNO相同,并在函数logblockinit中被初始化。

crash recovery

前面提到了redo log是用来实现ACID的持久性的,也就是只要事务提交成功后,事务内的所有修改都会保存到数据库,哪怕这时候数据库crash了,也要有办法来进行恢复。也就是Crash Recovery。

说到恢复,我们先来了解一个概念:什么是LSN?

LSN(log sequence number) 用于记录日志序号,它是一个不断递增的 unsigned long long 类型整数,占用8字节。它代表的含义有:

redo log写入的总量。 checkpoint的位置。 页的版本,用来判断是否需要进行恢复操作。 checkpoint:它是redo log中的一个检查点,这个点之前的所有数据都已经刷新回磁盘,当DB crash后,通过对checkpoint之后的redo log进行恢复就可以了。 我们可以通过命令show engine innodb status来观察LSN的情况:


LOG

Log sequence number 33646077360 Log flushed up to
33646077360 Last checkpoint at
33646077360 0 pending log writes ,

0 pending chkp writes 49687445 log i / o 's done, 1.25 log i/o' s / second

Log sequence number表示当前的LSN,Log flushed up to表示刷新到redo log文件的LSN,Last checkpoint at表示刷新到磁盘的LSN。如果把它们三个简写为 A、B、C 的话,它们的值的大小肯定为 A>=B>=C。

InnoDB引擎在启动时不管上次数据库运行时是否正常关闭,都会进行恢复操作。因为重做日志记录的是物理日志,因此恢复的速度比逻辑日志,如二进制日志要快很多。恢复的时候只需要找到redo log的checkpoint进行恢复即可。

3、Undo log

重做日志记录了事务的行为,可以很好的通过其对页进行“重做”操作。但是事务有时候还需要进行回滚操作,也就是ACID中的A(原子性),这时就需要Undo log了。因此在数据库进行修改时,InnoDB存储引擎不但会产生Redo,还会产生一定量的Undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户一条ROLLBACK语句请求回滚,就可以利用这些Undo信息将数据库回滚到修改之前的样子。

Undo log是InnoDB MVCC事务特性的重要组成部分。当我们对记录做了变更操作时就会产生Undo记录,Undo记录默认被记录到系统表空间(ibdata)中,但从5.6开始,也可以使用独立的Undo 表空间。

Undo记录中存储的是老版本数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着undo链找到满足其可见性的记录。当版本链很长时,通常可以认为这是个比较耗时的操作。

基本文件结构

为了保证事务并发操作时,在写各自的undo log时不产生冲突,InnoDB采用回滚段(Rollback Segment,简称Rseg)的方式来维护undo log的并发写入和持久化。回滚段实际上是一种 Undo 文件组织方式,每个回滚段又有多个undo log slot。具体的文件组织方式如下图所示:

上图展示了基本的Undo回滚段布局结构,其中: rseg0预留在系统表空间ibdata中。 rseg 1~rseg 32 这32个回滚段存放于临时表的系统表空间中,用于临时表的undo。 rseg33~rseg 128 则根据配置(InnoDB >= 1.1默认128,可通过参数 innodb_undo_logs 设置)存放到独立undo表空间中(如果没有打开独立Undo表空间,则存放于ibdata中,独立表空间可以通过参数 innodb_undo_directory 设置),用于普通事务的undo。 如图所示,每个回滚段维护了一个段头页,在该page中又划分了1024个slot(TRXRSEGN_SLOTS),每个slot又对应到一个undo log对象,因此理论上InnoDB最多支持 96 * 1024个普通事务。

Undo log的格式

在InnoDB引擎中,undo log分为:

insert undo log update undo log insert undo log是指在insert操作中产生的undo log,因为insert操作的记录,只对事务本身可见,对其他事务不可见(这是事务隔离性的要求),故该undo log可以在事务提交后直接删除,不需要进行purge操作。而update undo log记录的是delete和update操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除,提交时放入undo log链表,等待purge线程进行最后的删除。下面是两种undo log的结构图。

purge

对于一条delete语句 deletefromtwherea=1,如果列a有聚集索引,则不会进行真正的删除,而只是在主键列等于1的记录delete flag设置为1,即记录还是存在在B+树中。而对于update操作,不是直接对记录进行更新,而是标识旧记录为删除状态,然后新产生一条记录。那这些旧版本标识位删除的记录何时真正的删除?怎么删除?

其实InnoDB是通过undo日志来进行旧版本的删除操作的,在InnoDB内部,这个操作被称之为purge操作,原来在srvmasterthread主线程中完成,后来进行优化,开辟了purge线程进行purge操作,并且可以设置purge线程的数量。purge操作每10s进行一次。

为了节省存储空间,InnoDB存储引擎的undo log设计是这样的:一个页上允许多个事务的undo log存在。虽然这不代表事务在全局过程中提交的顺序,但是后面的事务产生的undo log总在最后。此外,InnoDB存储引擎还有一个history列表,它根据事务提交的顺序,将undo log进行连接,如下面的一种情况:

在执行purge过程中,InnoDB存储引擎首先从history list中找到第一个需要被清理的记录,这里为trx1,清理之后InnoDB存储引擎会在trx1所在的Undo page中继续寻找是否存在可以被清理的记录,这里会找到事务trx3,接着找到trx5,但是发现trx5被其他事务所引用而不能清理,故再去history list中取查找,发现最尾端的记录时trx2,接着找到trx2所在的Undo page,依次把trx6、trx4清理,由于Undo page2中所有的记录都被清理了,因此该Undo page可以进行重用。 InnoDB存储引擎这种先从history list中找undo log,然后再从Undo page中找undo log的设计模式是为了避免大量随机读操作,从而提高purge的效率。

4、多版本控制MVCC MVCC 多版本并发控制技术,用于多事务环境下,对数据读写在不加读写锁的情况下实现互不干扰,从而实现数据库的隔离性,在事务隔离级别为Read Commit 和 Repeatable Read中使用到,今天我们就用最简单的方式,来分析下MVCC具体的原理,先解释几个概念。

InnoDB存储引擎的行结构

InnoDB表数据的组织方式为主键聚簇索引,二级索引中采用的是(索引键值, 主键键值)的组合来唯一确定一条记录。

InnoDB表数据为主键聚簇索引,mysql默认为每个索引行添加了4个隐藏的字段,分别是:

DBROWID:InnoDB引擎中一个表只能有一个主键,用于聚簇索引,如果表没有定义主键会选择第一个非Null的唯一索引作为主键,如果还没有,生成一个隐藏的DBROWID作为主键构造聚簇索引。 DBTRXID:最近更改该行数据的事务ID。 DBROLLPTR:undo log的指针,用于记录之前历史数据在undo log中的位置。 DELETE BIT:索引删除标志,如果DB删除了一条数据,是优先通知索引将该标志位设置为1,然后通过(purge)清除线程去异步删除真实的数据。

整个MVCC的机制都是通过 DB_TRX_ID, DB_ROLL_PTR这2个隐藏字段来实现的。

事务链表

当一个事务开始的时候,会将当前数据库中正在活跃的所有事务(执行begin,但是还没有commit的事务)保存到一个叫 trx_sys的事务链表中,事务链表中保存的都是未提交的事务,当事务提交之后会从其中删除。

转载于:https://my.oschina.net/hensemlee/blog/1803367

谈谈MySQL InnoDB存储引擎事务的ACID特性相关推荐

  1. 浅析Mysql InnoDB存储引擎事务原理

    浅析Mysql InnoDB存储引擎事务原理 大神:http://blog.csdn.net/tangkund3218/article/details/47904021

  2. 为什么MySQL InnoDB 存储引擎要用B+树做索引,而不用B树?

    为什么MySQL InnoDB 存储引擎 要用B+树做索引,而不用B树? (1)B+树空间利用率更高,可减少I/O次数 一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存 ...

  3. mysql InnoDB存储引擎的介绍

    mysql InnoDB存储引擎的介绍 概念 1.InnoDB是MySQL默认的存储引擎,如果需要其不支持的特性,则考虑使用其他存储发动机. 2.InnoDB采用MVCC支持高并发,实现四个标准隔离级 ...

  4. MySQL Innodb存储引擎使用B+树做索引的优点

    对于数据库来说,索引和表数据都是存放在磁盘上的,一般使用B+树作为索引 MySQL Innodb存储引擎使用了B+树作为索引的优点,主要有以下原因: 1.索引和表数据都是存放在磁盘上的,如果磁盘上的数 ...

  5. MySQL InnoDB 存储引擎索引那些事儿

    InnoDB 存储引擎中,表是根据主键顺序组织存放的,称为索引组织表.每个表都有一个主键,如果没有显示定义主键,则会选择第一个创建的非空唯一索引作为主键,如果没有非空唯一索引,InnoDB引擎则自动创 ...

  6. MySQL InnoDB存储引擎

    呵呵哒... MySQL体系结构和存储引擎 首先要搞懂的是什么是数据库,什么是数据库实例. 数据库:物理操作系统文件或其他形式文件类型的集合. 实例:MySQL数据库由后台线程以及一个共享内存区组成, ...

  7. mysql innodb 锁类型_详细介绍MySQL InnoDB存储引擎各种不同类型的锁

    本文中,我们详细介绍MySQLInnoDB存储引擎各种不同类型的锁,以及不同SQL语句分别会加什么样的锁. 阅读提示 1.本文所参考的MySQL文档版本是8.0,做实验的MySQL版本是8.0.13 ...

  8. MySQL InnoDB 存储引擎文件

    InnoDB 存储引擎文件,为该存储引擎独有,包括表空间文件和重做日志文件. 1.表空间文件 InnoDB存储引擎的数据按照表空间进行存放,默认配置下有一个初始大小和默认名的文件,通过以下命令查看: ...

  9. mysql innodb 存储引擎

    --MySQL 结构有两部分组成 1.MySQL server 层 2.存储引擎层 --注:到 存储引擎层之前都属于 MySQL server 层 MySQL 5.1到 5.7 ,大版本 没有变化 , ...

最新文章

  1. TSQL语句中的Like用法
  2. 计算机二级c真题108套,2016年计算机二级108套程序.docx
  3. 全國身份證查詢系統nciis
  4. 表格合并行_Word制作验收单表格,很简单,快来学习吧
  5. openlayer右键菜单_使用OpenLayers3 添加地图鼠标右键菜单
  6. 通过cordova将vue项目打包
  7. hashmap 遍历_这21个刁钻的HashMap面试题,我把阿里面试官吊打了
  8. NoSQL(3) 之Redis主从复制、哨兵和集群介绍及详细搭建步骤
  9. ACM PKU1703 Find them, Catch them
  10. 介绍几款开源好用的产品
  11. 终端定时任务 开始缓冲_如何开始使用终端以提高生产力
  12. 模型预测控制的缺点_【电子技术】【2018.01】模型预测控制FPGA实现的协同设计...
  13. R语言作图之ggplot2作图2
  14. 机器学习笔记 - 什么是支持向量回归(SVR)?
  15. 如何替换mac word中的换行符为空格
  16. netgen.5.0.0下载地址与Windows下编译方法
  17. Docker Dockerfile 验证Docker内部使用jmap报错问题解决
  18. 微信小程序实现下拉刷新功能
  19. 京东2019年春招题(前端)
  20. java.lang.ClassCastException: java.util.Arrays$ArrayList cannot be cast to java.util.ArrayList

热门文章

  1. BestCoder Round #14 B 称号 Harry And Dig Machine 【TSP】
  2. 如何用Python实现目录遍历
  3. ReportView使用
  4. RHEL7及CentOS7的语言、字符编码、键盘映射、X11布局设置(localectl)-系统管理(1)...
  5. 安全漏洞问题6:SQL注入
  6. 管理数据,应用程序和主机安全-A
  7. 顺藤摸瓜的解决GDB的DEBUG中出现的小问题
  8. 配置管理系统和整体的变化对系统有什么区别和联系
  9. 编译安装nginx-1.6.0
  10. Rocky4.2下安装金仓v7数据库(KingbaseES)