目录
- 思维导图
- 主要内容
- 日志分类
- 详细介绍undo log/binlog/redo log
- binlog和redo log的区别/联系/崩溃恢复
- update语句执行流程
1. 思维导图
2. 主要内容
2.1 MySQL的日志
2.1.1 日志分类
- 错误日志(error log):错误日志文件对 MySQL 的启动、运行、关闭过程进行了记录,能帮助 定位 MySQL 问题。
- 慢查询日志(slow query log):慢查询日志是用来记录执行时间超过 long_query_time 这个变 量定义的时长的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率很低,以便进行 优化。
- 一般查询日志(general log):一般查询日志记录了所有对 MySQL 数据库请求的信息,无论请求是否正确执行。
- 二进制日志(bin log):关于二进制日志,它记录了数据库所有执行的 DDL 和 DML 语句(除 了数据查询语句 select、show 等),以事件形式记录并保存在二进制文件中。
- 重做日志(redo log): InnoDB 存储引擎特有, 记录了对于 InnoDB 存储引擎的事务日 志。
- 回滚日志(undo log): InnoDB 存储引擎特有, 作用就是对数据进行回滚。当事务对数据库进行修改,InnoDB 引擎不仅会记录 redo log,还会生 成对应的 undo log 日志;如果事务执行失败或调用了 rollback,导致事务需要回滚,就可以利 用 undo log 中的信息将数据回滚到修改之前的样子。
2.1.2 undo log
- 作用:
- 记录数据被修改前的版本
- 记录数据的逻辑变化
- 提供多版本并发控制下的读(MVCC),也即非锁定读
- 内容:逻辑格式的日志,undo回滚是逻辑回滚
- 版本链:
- 行记录里的隐藏字段:
- trx_id: 增删改操作时,InnoDb分配唯一的事务id,新记录时为null
- roll_pointer: 指向行记录的最近一条undo log
- 增删改产生的 undo log 通过old roll_pointer指向上一个版本的undo log记录,连成一个单向
- 行记录里的隐藏字段:
2.1.3 binlog
- 作用:
- 因为是追加写的机制,可以作为归档历史日志
- 从库重放主库数据库变更,实现主从复制高可用
- 作为下游异构数据的触发更新
- 模式:
- statement: 记录逻辑语句,即执行的sql, 一些用到索引的sql可能会有主备执行结果不一致的问题
- row: 记录表数据的物理变更,记录的范围为主键id,不会有主备执行结果不一致的问题。缺点是如果一条sql更改了很多数据,row会比statement占用更多的磁盘空间,另外一次性往写大量的数据,还要消耗io资源,降低执行速度。
- mixed: MySQL 自己会判断这条 SQL 语句是否可能引起主备不一致,如果有可能,就用 row 格式,否则就用 statement 格式。mixed可以在相对节省空间的情况下避免数据不一致的问题。
- 尽管mixed更节省空间,但是出于恢复数据的考虑,更多的还是会用row,因为row的记录修改成恢复的sql非常简单:
- insert: 直接转delete
- update: 字段值前后对调
- delete: 直接转insert
2.1.4 redo log
mysql,如果每次更新操作都要写进磁盘,然后磁盘要找到对应记录,然后再更新,整个过程 io 成本、查找成本都很高。解决方案:WAL 技术(Write-Ahead Logging)。先写日志,再写磁盘。
具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。 write pos: 写入游标,标记哪些区域已经写了日志但是没有写磁盘 checkpoint: 标记往前的部分就是没有写磁盘的新日志 write pos 追上checkpoint, 表明循环区域已经写满了,则停止写redo log,进行写磁盘
2.1.5 两阶段提交
-
两阶段提交
之所以redo log的write和fsync没有连接在一起,其实是考虑到了组提交的功能,分开来进行这两个步骤,在并发的场景下,可以让这一组一次性提交的redo log更多一点,从而一次性fsync更多的组员。
-
如果redo log和binlog不是两阶段提交,而是先写一个再写另一个,会有什么问题?
- 先写redo log再写binlog,若redo log写完,写binlog前崩溃: 重启后数据可以恢复,但是binlog里没有相关记录,从库里和异构数据里无法回放这一变更
- 先写binlog再写redo log, 若binlog写完,写redo log前崩溃: 因为redo log未记录,所以事务无效,主库里不会有这次变更,又因为binlog已写入变更的记录,从库里会重放这次变更,导致主从数据不一致
- 因此两阶段提交就是提供一个机会,两边都确认可以了再提交
-
innodb采用了WAL技术(先写log,再写磁盘)来减少磁盘写。上面的分析过程不难看出,在事务提交的过程中,redo log prepare阶段会进行一次fsync磁盘,binlog 阶段也会fsync一次磁盘,这似乎并没有减少与磁盘的交互,MySQL这样设计的意义是什么?
- redo log和binlog都是顺序写,顺序写比数据页的随机写节约时间,性能更高
- 组提交机制使得我们不用每个事务都进行写磁盘操作,而是将多个写操作放在一个组里面,这样可以大幅度降低磁盘的IOPS
2.1.6 binlog 和redo log的关联与区别:
- 区别:
- 定位: redo log 是 InnoDB 引擎特有的, 相当于是插件;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用,是原生的机制。
- 记录内容: bin log 在statement模式下是逻辑日志,在row模式下记录的是数据物理变更情况。而 redo log 记录的是关于每个页(Page)的更改的物理情况。
- 写入时间: bin log 仅在事务提交前进行提交,也就是只写磁盘一次。而在事务进行的过程 中,却不断有 redo ertry 被写入 redo log 中。
- 写入方式: redo log 是循环写入和擦除,bin log 是追加写入,不会覆盖已经写的文件
- 关联: 有一个共同的数据字段,叫 XID。崩溃恢复的时候,会按顺序扫描 redo log:如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务。
2.1.7 崩溃恢复
- 崩溃恢复
- 写入 redo log,prepare 阶段之后、写 binlog 之前,发生了崩溃,由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚
- binlog 写完,redo log 还没 commit 前发生 crash:
- 如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交
- 如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整: 完整则提交事务, 否则回滚事务。
- binlog完整性判断, 基于格式:
- statement 格式的 binlog,最后会有 COMMIT
- row 格式的 binlog,最后会有一个 XID event。
- 在 MySQL 5.6.2 版本以后,还引入了 binlog-checksum 参数,用来验证 binlog 内容的正确性。对于 binlog 日志由于磁盘原因,可能会在日志中间出错的情况,MySQL 可以通过校验 checksum 的结果来发现。
2.1.8 update语句执行流程
更新语句的执行是 Server 层和引擎层配合完成,数据除了要写入表中,还要记录相应的日志。
- 执行器先找引擎获取 ID=2 这一行。ID 是主键,存储引擎检索数据,找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然 后再返回。
- 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数 据,再调用引擎接又写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处 于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
- 执行器调用引擎的提交事务接又,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。 MySQL 在执行更新语句的时候,在服务层进行语句的解析和执行,在引擎层进行数据的提取和存储;同时在服务层对 binlog 进行写入,在 InnoDB 内进行 redo log 的写入。 不仅如此,在对 redo log 写入时有两个阶段的提交,一是 binlog 写入之前 prepare 状态的写入,二是 binlog 写入之后 commit 状态的写入。