日志篇
# 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;
- 执行器负责具体执行,调用存储引擎接口,通过主键索引树搜索获取 id = 1 这一行记录:
- 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
- 否则将数据页从磁盘读入到 buffer pool,返回记录给执行器;
- 执行器得到聚簇索引记录后,检查更新前的记录和更新后的记录是否一样;
- 不一样就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
- 否则不进行后续更新流程;
- 开启事务,InnoDB层更新记录前,首先记录undo log,undo log会写入Buffer Pool中的undo 页面;
- InnoDB层开始更新记录,先更新内存(同时标记为脏页),然后将记录写到redo log中,这时候更新算完成。为减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘;
- 至此,一条记录更新完成;
- 一条更新语句完成后,然后开始记录该语句对应的binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘;
- 事务提交(两阶段提交);
# insert语句执行过程
- 如果数据页在内存中,直接更新内存;
- 如果不在内存中,就在change buffer中记录下“往该页插入一行“这个信息;
- 写入redo log中;
# select语句执行过程
- 如果数据页在Buffer Pool中,直接从内存中读取数据;否则从磁盘中加载;
- 查询优化:优化器根据表索引和统计信息等数据,选择最优的执行计划,执行计划包括扫描表,使用索引,执行连接操作等步骤;
# 两阶段提交
为什么需要?
事务提交后,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;