该篇其实重点涉及两个日志的使用和处理。

一个是 server 层的 binlog 一个是服务器层的 redolog。

首先还是根据主线来介绍当我们在执行更新语句的时候我们在做什么

Redo Log

MySQL 使用一种叫 WAL(Write-Ahead Logging) 的技术,它达到的效果是,先写日志后写磁盘。

在计算机科学中,预写式日志(Write-ahead logging,縮寫WAL)是关系数据库系统中用于提供原子性和持久性(ACID属性中的两个)的一系列技术。 在使用WAL的系统中,所有的修改在提交之前都要先写入log文件中。

在 MySQL 中,我们在执行一条更新语句的时候会先将记录写到 redo log 里面并更新内存,这个时候更新就算完成了。之后 InnoDB 会在适当的时候把这个操作记录更新到磁盘里面。(多提一嘴,后面会介绍,其实在更新之前还写了 undo log)redo log 在刷新之前会被记录到 buffer 中,这个 buffer 大小由 MySQL 参数 Innodb_log_buffer 控制,默认大小是 8M。

之后会通过三种方式刷新到磁盘:

1,Master Thread 每秒一次执行刷新Innodb_log_buffer到重做日志文件。
2,每个事务提交时会将重做日志刷新到重做日志文件。
3,当重做日志缓存可用空间少于一半时,重做日志缓存被刷新到重做日志文件。

被我们刷新到磁盘的 redolog 通常长这个样子:

可以看到是固定大小 512M 的文件,这取决于我们设置的参数 innodb_log_file_size 的大小。默认情况下 innodb_log_files_in_group 参数为 2 会为我们循环保存两个日志文件。保存的默认路径就是数据存放路径。

与写内存 buffer 差不多,写到磁盘的 redolog 也是会循环覆盖的。

wirte pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。 checkoutpoint 就是当前要擦除的位置,也是往后推移并且循环的。擦除记录前要把记录更新到真正的数据文件中。绿色的部分是还没有写满的部分,如果 write pos 追上了 check point 就表示写满了,这个时候不能再执行更新,必须停下来把数据刷进数据文件,然后把 check point 往后移。有了这个机制就可以保证 Innodb 发生异常重启当前提交记录不会丢失,因为事务开始的时候就已经被记录进了 redo log 中了。

Binlog Log

上面一直谈的 redo log 其实是存储引擎层的 crash-safe 机制。binlog 是在 server 层的机制,是跨存储引擎层的机制。redolog 记录的是物理上的在哪个数据页上修改了什么数据,而 binlog 是记录的执行了什么语句修改了什么数据。binlog 并不会像 redolog 那样覆盖写,他会一直以增量文件的形式存在。默认单个 binlog 可以写 1g 文件,写满 1g 会另开新的文件继续写入。 binlog 用来重放数据和做 slave 非常的方便好用。

Update 的执行

mysql> update T set c=c+1 where ID=2;

1. 执行器先找到 id=2 这一行,如果 id=2 这一行本来就在内存中,就直接返回给执行器,不然需要从磁盘读入内存,然后再返回。

2. 记录 c=0 到 undo log 然后执行器拿到引擎给的数据加 1,再调用新的接口写入这行数据。

3. 引擎将这行新的数据更新到内存中,同时将这个更新操作记录到 redo log 中,此时 redo log 处于 preprare 状态。然后告知执行器处理完成,随时可以提交事务。

4. 执行器负责生成这个操作的 binlog 并把 binlog 写入磁盘。

5. 执行调用引擎提交事务接口,引擎把刚刚写入的 redo log 改为提交状态更新完成。

可能我们都注意到了 redo log 有两个状态,preparre 和 commit 。这就是著名的两阶段提交。

要进行两阶段提交的目的就是最终让两个日志文件,也就是 redo log 和 binglog 在逻辑上保持一致。如果不进行两阶段提交,不管是先写 redo log 还是 binlog 都会可能出现不一致的情况。

先写 redo log 再写 binlog :

c = 0
mysql> update T set c=c+1 where ID=2;

假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启,由于我们之前谈到过 redo log 写完之后系统即使崩溃,仍然能够把数据恢复回来,所以恢复之后由于 redolog 写完了我们恢复回来 c 应该等于 1 。但是由于 binlog 没有写完就 crash 了,这个时候 binlog 里面就没有这个更新语句的记录,因此,之后备份日志的时候,存起来的 binlog 就没有这个语句。之后如果使用 binlog 来恢复临时库就会发生不一致。

先写 binlog 后写 redo log:

假设在 binlog 写完之后 crash, 由于 rerdo log 还没有写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0.但是 binlog 里面已经记录了 把 c +1 这条更新记录的日志。所以 binlog 来恢复的时候就多出一个语句,与原库同样不一致。

在这种情况下,如果我们是两阶段提交,我们来分析下面几种场景:

1. prepare 阶段,redo log 落盘前,MySQL crash
这种情况由于 redo log 都还没有落盘,事务并没有正确写入磁盘,数据的一致性会有问题。
 
2. prepare 阶段,redo log 落盘后,binlog 落盘前 MySQL crash
这种情况下 redo log 已经落盘,但是 binlog 却没有写入,会通过执行回滚来保证数据库的一致性。
 
3. commit 阶段,binlog 落盘后,但 commit 前 MySQL crash

这种情况下我们可以搜集到 binlog 落盘后的信息可以直接提交 binlog event。

简单的总结一下就是服务器 crash 后 MySQL 内部事务如果 binlog 已经落盘则事务应该被提交,如果 binlog 未落盘事务应该被回滚。

不得不再提一点,跟这一块紧密关联的还有两个地方,为了保证严格的数据不丢失,我们应该设置 innodb_flush_log_at_trx_commit 和 sync_binlog 为 1。

innodb_flush_log_at_trx_commit:

innodb_flush_log_at_trx_commit=0,事务发生过程,日志一直积攒在redo log buffer中,跟其他设置一样,但是在事务提交时,不产生 redo 写操作,而是 MySQL 内部每秒操作一次,从 redo log buffer,把数据写入到系统中去。如果发生 crash,即丢失1s内的事务修改操作。
innodb_flush_log_at_trx_commit=1,每次 commit 都会把 redo log 从 redo log buffer 写入到 system,并 fsync 刷新到磁盘文件中。
innodb_flush_log_at_trx_commit=2,每次事务提交时 MySQL 会把日志从 redo log buffe 写入到 system,但只写入到 file system buffer,由系统内部来 fsync 到磁盘文件。如果数据库实例crash,不会丢失redo log,但是如果服务器 crash,由于 file system buffer 还来不及 fsync 到磁盘文件,所以会丢失这一部分的数据。
所以我们设置成 1,每次 commit 都从 log buffer 里面拿出来写入到 system 然后刷到磁盘中去。

sync_binlog:

默认,sync_binlog=0,表示 MySQL不控制 binlog 的刷新,由文件系统自己控制它的缓存的刷新。这时候的性能是最好的,但是风险也是最大的。因为一旦系统Crash,在 binlog_cache 中的所有binlog 信息都会被丢失。

如果sync_binlog>0,表示每sync_binlog次事务提交,MySQL调用文件系统的刷新操作将缓存刷下去。最安全的就是 sync_binlog=1 了,表示每次事务提交,MySQL 都会把 binlog 刷下去,是最安全但是性能损耗最大的设置。这样的话,在数据库所在的主机操作系统损坏或者突然掉电的情况下,系统才有可能丢失1个事务的数据。但是 binlog 虽然是顺序IO,但是设置 sync_binlog=1,多个事务同时提交,同样很大的影响 MySQL 和 IO 性能。虽然可以通过 group commit 的补丁缓解,但是刷新的频率过高对 IO 的影响也非常大。对于高并发事务的系统来说,"sync_binlog" 设置为 0 和设置为1的系统写入性能差距可能高达 5 倍甚至更多。

最后还想补充一点我之前遇到的一个坑, MySQL 在刷新 binlog 到磁盘的时候完全有可能是该事务还没有提交的时候,即使你把 sync_binlog 设置成 1 也可能出现这种情况。就像定期刷新或者当一个事务提交了执行刷盘操作,也带上了还没有提交 commit 但是却完成了更新的操作。这一点比较容易发生在 master slave 同步上面,当 master 操作了更新, slave 去同步类似的操作, binlog 里面明明已经看到更新操作了,也去更新,但是去 master 上面查询相关的数据竟然还是旧的值没有被更新

【MySQL 读书笔记】当我们在执行更新语句的时候我们在做什么的更多相关文章

  1. 【MySQL 读书笔记】当我们在使用索引的时候我们在做什么

    我记得之前博客我也写过关于索引使用的文章,但是并不全面,这次尽量针对重点铺全面一点. 因为索引是数据引擎层的结构我们只针对最常见使用的 Innodb 使用的 B+Tree 搜索树结构进行介绍. 每一个 ...

  2. 【MySQL 读书笔记】当我们在执行该查询语句的时候我们在干什么

    看了非常多 MySQL 相关的书籍和文章,没有看到过如此优秀的专栏.所以未来一段时间我会梳理读完该专栏的所学所得. 当我们在执行该查询语句的时候我们在干什么 mysql> select * fr ...

  3. 【MySQL 读书笔记】SQL 刷脏页可能造成数据库抖动

    开始今天读书笔记之前我觉得需要回顾一下当我们在更新一条数据的时候做了什么. 因为 WAL 技术的存在,所以当我们执行一条更新语句的时候是先写日志,后写磁盘的.当我们在内存中写入了 redolog 之后 ...

  4. 【MySQL 读书笔记】普通索引和唯一索引应该怎么选择

    通常我们在做这个选择的时候,考虑得最多的应该是如果我们需要让 Database MySQL 来帮助我们从数据库层面过滤掉对应字段的重复数据我们会选择唯一索引,如果没有前者的需求,一般都会使用普通索引. ...

  5. 【MySQL 读书笔记】RR(REPEATABLE-READ)事务隔离详解

    这篇我觉得有点难度,我会更慢的更详细的分析一些 case . MySQL 的默认事务隔离级别和其他几个主流数据库隔离级别不同,他的事务隔离级别是 RR(REPEATABLE-READ) 其他的主流数据 ...

  6. 【MySQL 读书笔记】全局锁 | 表锁 | 行锁

    全局锁 全局锁是针对数据库实例的直接加锁,MySQL 提供了一个加全局锁的方法, Flush tables with read lock 可以使用锁将整个表的增删改操作都锁上其中包括 ddl 语句,只 ...

  7. 【MySQL 读书笔记】“order by”是怎么工作的?

    针对排序来说,order by 是我们使用非常频繁的关键字.结合之前我们对索引的了解再来看这篇文章会让我们深刻理解在排序的时候,是如何利用索引来达到少扫描表或者使用外部排序的. 先定义一个表辅助我们后 ...

  8. MySQL笔记(6)-- SQL更新语句日志系统流程

    一.背景 在上一篇[MySQL笔记(5)-- SQL执行流程,MySQL体系结构]中讲述了select查询语句在MySQL体系中的运行流程,从连接器开始,到分析器.优化器.执行器等,最后到达存储引擎. ...

  9. QtSQL学习笔记(3)- 执行SQL语句

    QSqlQuery类提供了一个用于执行SQL语句和浏览查询的结果集的接口. QSqlQueryModel和QSqlTableModel类提供了一个用于访问数据库的高级接口,这将在下一节介绍.如果你不熟 ...

随机推荐

  1. spring-security实现的token授权

    在我的用户密码授权文章里介绍了spring-security的工作过程,不了解的同学,可以先看看用户密码授权这篇文章,在 用户密码授权模式里,主要是通过一个登陆页进行授权,然后把授权对象写到sessi ...

  2. Golang: Cobra命令行参数库的使用

    将cobra下载到 $GOPATH,用命令: go get -v github.com/spf13/cobra/cobra 然后使用 go install github.com/spf13/cobra ...

  3. 浅谈mybatis如何半自动化解耦

    在JAVA发展过程中,涌现出一系列的ORM框架,JPA,Hibernate,Mybatis和Spring jdbc,本系列,将来研究Mybatis. 通过研究mybatis源码,可将mybatis的大 ...

  4. Redis分布式队列和缓存更新

    原文链接:https://www.cnblogs.com/hua66/p/9600085.html 在使用Redis中,我们可能会遇到以下场景: 例如: 某用户向服务器中发送一个请求,服务器将用户请求 ...

  5. Springboot整合activemq

    今天呢心血来潮,也有很多以前的学弟问到我关于消息队列的一些问题,有个刚入门,有的有问题都来问我,那么今天来说说如何快速入门mq. 一.首先说下什么是消息队列? 1.消息队列是在消息的传输过程中保存消息 ...

  6. C++系列总结——继承

    前言 前面讲了封装,但封装只是隐藏了类内部实现.如果使用多态隐藏类本身的话,只有封装是不够的,还需要继承. 继承 通过封装.我们把一些相关的函数和变量包裹在了一起,这些函数和变量就叫做类的成员函数和成 ...

  7. python 练习 后台返回当前时间

    新建一个 current_time.html 文件, !cur_time! 用来替换 <!DOCTYPE html> <html lang="en"> &l ...

  8. Android项目实战欢迎界面

    欢迎界面 首先同理把欢迎界面的图片导入到drawable目录下,在导入时 Android Studio 会提示如下 drawable 具体本人尚未弄明白,待理解后会重新补全本部分内容,在此本人选了第一 ...

  9. Linux下使用yum安装软件命令

    1.yum list | grep 要下载的文件名字2.yum install 完整文件名字3.rpm -qa | grep 软件名字 //查看版本

  10. 在 asp.net core 中使用类似 Application 的服务

    在 asp.net core 中使用类似 Application 的服务 Intro 在 asp.net 中,我们可以借助 Application 来保存一些服务器端全局变量,比如说服务器端同时在线的 ...