Home
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • JavaSE
  • JVM
  • JUC
  • Netty
  • CPP
  • QT
  • UE
  • Go
  • Gin
  • Gorm
  • HTML
  • CSS
  • JavaScript
  • vue2
  • TypeScript
  • vue3
  • react
  • Spring
  • SpringMVC
  • Mybatis
  • SpringBoot
  • SpringSecurity
  • SpringCloud
  • Mysql
  • Redis
  • 消息中间件
  • RPC
  • 分布式锁
  • 分布式事务
  • 个人博客
  • 弹幕视频平台
  • API网关
  • 售票系统
  • 消息推送平台
  • SaaS短链接系统
  • Linux
  • Docker
  • Git
GitHub (opens new window)
Home
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 设计模式
  • JavaSE
  • JVM
  • JUC
  • Netty
  • CPP
  • QT
  • UE
  • Go
  • Gin
  • Gorm
  • HTML
  • CSS
  • JavaScript
  • vue2
  • TypeScript
  • vue3
  • react
  • Spring
  • SpringMVC
  • Mybatis
  • SpringBoot
  • SpringSecurity
  • SpringCloud
  • Mysql
  • Redis
  • 消息中间件
  • RPC
  • 分布式锁
  • 分布式事务
  • 个人博客
  • 弹幕视频平台
  • API网关
  • 售票系统
  • 消息推送平台
  • SaaS短链接系统
  • Linux
  • Docker
  • Git
GitHub (opens new window)
  • SQL编程50题
  • 基础篇
  • 索引篇
  • 事务篇
  • 锁篇
  • 日志篇
    • undo log
    • Buffer Pool
    • redo log
    • bin log
    • update语句执行过程
    • insert语句执行过程
    • select语句执行过程
    • 两阶段提交
  • 高可用篇
  • 分库分表
  • 性能优化
  • Mysql
Nreal
2023-11-15
目录

日志篇

# undo log

为什么需要?

保证数据库事务ACID特性的 原子性;

在事务没有提交之前,Mysql会先将记录更新前的数据到undo log日志文件中,当事务回滚时,利用undo log进行回滚;

一条记录的每一次更新操作产生的undo log格式都有一个roll_pointer指针和一个trx_id 事务id:

  • 通过 trx_id 可以知道该记录是被哪个事务修改的;
  • 通过 roll_pointer 指针可以将这些 undo log 串成一个链表,这个链表就被称为版本链;

undo log另一个作用:通过ReadView+undo log实现MVCC;

读已提交 和 可重复读 隔离级别区别在于生成ReadView的时机不同:

  • 读已提交:每个select都生成一个新的Read View,事务期间 多次读取同一条数据,前后两次数据不一致,可能期间另一个事务修改了该记录,并提交;
  • 可重复度:启动事务时生成一个ReadView,整个事务都在用这个ReadView,保证 事务期间读到的数据都是事务启动前的记录;

这两个隔离级别实现通过 ReadView里的字段 和 undolog中的事务id与回滚指针 的比对,如果不满足可见性就会顺着undo log版本链找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制);

# Buffer Pool

为什么需要?

Mysql的数据存在磁盘当中,更新记录时,得先从磁盘读取该记录,再在内存中修改,修改完先缓存起来,下次有查询语句命中,直接读取缓存中的记录,不需要再从磁盘中获取了;

修改完缓存起来的数据页设置为脏页,为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘;

查询一条记录,只需要缓冲一条记录吗?

缓冲整个页,再铜鼓页目录去定位到某条具体的记录;

# redo log

为什么需要?

保证事务ACID中的 持久性,保证Mysql任何时间崩溃,重启后之前已提交的记录不会丢失;

Buffer Pool基于内存,万一断电重启,还没来得及落盘的脏页数据就会丢失。

为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,此时更新才算完成,这就是WAL技术;

WAL一个优点:

redo log写磁盘是顺序写,性能高,相比较于数据写磁盘随机写,要先找到写入位置才写到磁盘;

什么是redo log?

物理日志:记录了某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新;

事务提交时,只要先将 redo log 持久化到磁盘即可,可以不需要等到将缓存在 Buffer Pool 里的脏页数据持久化到磁盘;

与undo log区别?

undo log记录的是事务开始前的数据状态;事务提交前崩溃,重启后通过undo log回滚;

redo log记录的是事务完成后的数据状态;事务提交后崩溃,重启后通过redo log恢复事务;

redo log是直接写入磁盘的吗?

通过自己的缓存redo log buffer;

刷盘时机:

  • 当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘;
  • InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘;
  • 每次事务提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘;

redo log文件写满怎么办?

InnoDB存储引擎有1个重做日志文件组,它有2个redo log文件组成;

重做日志文件组以循环写方式工作,从头开始写,写到末尾就又回到开头,相当于一个环形;

# bin log

为什么需要?

bin log记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类的操作;

与redo log区别?

  • binlog 是Server层实现的日志,所有存储引擎都能用,redo log是InnoDB存储引擎实现的日志;

  • 文件格式:binlog有3种格式类型: STATEMENT(默认格式)、ROW、 MIXED;

    STATEMENT:每一条修改数据的sql都会被记录到binlog(逻辑日志);

    ROW:记录行数据最终被修改成什么样(不是逻辑日志),缺点是每行数据变化结果都会被记录,比如执行批量 update 语句,更新多少行数据就会产生多少条记录,使binlog文件过大,而在 STATEMENT 格式下只会记录一个 update 语句而已;

    redo log是物理日志:记录的是在某个数据页做了什么修改,比如对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新;

  • 写入方式:binlog是追加写,写满一个文件,创建一个新的文件继续写,不会覆盖,保存的是全量日志;

    redo log是循环写,日志空间大小固定;

  • 用途:binlog用于备份恢复,主从复制;redolog用于故障恢复;

redo log可以将整个数据库数据删除恢复吗?

不可以;

redo log是循环写,边写边擦除,只记录未被刷入磁盘的数据的物理日志,已经刷入磁盘的数据都会从 redo log 文件里擦除;

bin log文件保存的是全量日志,所有数据变更的情况,如果不小心整个数据库的数据被删除了,得用 binlog 文件恢复数据;

bin log刷盘时机?

事务执行过程,先把日志写到binlog cache(Server层的cache),事务提交时,再把 binlog cache 写到 binlog 文件中;

一个线程只能同时有一个事务执行,每个线程有自己的binlog cache,但是最终都写到同一个 binlog 文件,因此binlog不能被拆分:

Mysql提供一个 sync_binlog 参数来控制数据库的 binlog 刷到磁盘上的频率:

  • sync_binlog = 0 的时候,表示每次提交事务都只 write,不 fsync,后续交由操作系统决定何时将数据持久化到磁盘;
  • sync_binlog = 1 的时候,表示每次提交事务都会 write,然后马上执行 fsync;
  • sync_binlog =N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync;

# update语句执行过程

更新记录UPDATE t_user SET name='Nreal' WHERE id=1;

  1. 执行器负责具体执行,调用存储引擎接口,通过主键索引树搜索获取 id = 1 这一行记录:
    • 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
    • 否则将数据页从磁盘读入到 buffer pool,返回记录给执行器;
  2. 执行器得到聚簇索引记录后,检查更新前的记录和更新后的记录是否一样;
    • 不一样就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
    • 否则不进行后续更新流程;
  3. 开启事务,InnoDB层更新记录前,首先记录undo log,undo log会写入Buffer Pool中的undo 页面;
  4. InnoDB层开始更新记录,先更新内存(同时标记为脏页),然后将记录写到redo log中,这时候更新算完成。为减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘;
  5. 至此,一条记录更新完成;
  1. 一条更新语句完成后,然后开始记录该语句对应的binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘;
  2. 事务提交(两阶段提交);

# insert语句执行过程

  1. 如果数据页在内存中,直接更新内存;
  2. 如果不在内存中,就在change buffer中记录下“往该页插入一行“这个信息;
  3. 写入redo log中;

# select语句执行过程

  1. 如果数据页在Buffer Pool中,直接从内存中读取数据;否则从磁盘中加载;
  2. 查询优化:优化器根据表索引和统计信息等数据,选择最优的执行计划,执行计划包括扫描表,使用索引,执行连接操作等步骤;

# 两阶段提交

为什么需要?

事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致;

案例:UPDATE t_user SET name='Nreal' WHERE id=1;持久化redo log和binlog两个日志过程中,出现半成功状态的两种情况;

  • 如果在将 redo log 刷入到磁盘之后, MySQL 突然宕机了,而 binlog 还没有来得及写入;Mysql重启后,通过redo log能将name字段恢复,但是binlog没有记录这条更新语句,主从架构中,binlog 会被复制到从库,由于 binlog 丢失了这条更新语句,从库的这一行 name 字段是旧值,与从库不一致;
  • 如果在将 binlog 刷入到磁盘之后, MySQL 突然宕机了,而 redo log 还没有来得及写入;从库根据binlog执行更新语句,主库字段没有恢复,出现不一致;

事务提交两阶段提交流程:

事务提交过程两个阶段,将redo log写入拆分成两个步骤:pre和commit,中间穿插写入binlog;

锁篇
高可用篇

← 锁篇 高可用篇→

Theme by Vdoing | Copyright © 2021-2024
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式