三大日志
执行一条select语句要经过的步骤
- 连接器
- 与客户端进行TCP三次握手进行连接
- 查询缓存
- MySQL8.0以后就将这部分给删掉了
- 解析SQL
- 词法分析(分析出Token和KeyWard(select和from等))
- 语法分析根据语法规则判断出MySQL语句如果没问题会构建出SQL语法树,如果语法不对会报错(from写成form)
- 但是表不存在或字段不存在不是在这检测出的
- 执行SQL
- 预处理
- 在这个阶段会检测出表不存在或者字段不存在等错误
- 优化器
- 如果查询时可以选择多个索引,那优化器会基于成本考虑去选择一个合适的执行方案
- 执行器
- 按照优化器选择的执行方案去执行SQL语句,从存储引擎读取记录返回给客户端
- 预处理
执行一条update语句
执行update的操作也会发生执行select的操作,但是新增了
- 去buffer Pool读取数据
- 如果读取到,直接返回个执行器
- 没读到,去磁盘中读入数据到buffer Pool中
- 比对更新前后的数据
- 如果相同那就不进行更新流程
- 不同的话那就把更新前的数据和更新后的数据都传给InnoDB层,让InnoDB层执行更新
- 开启事务,首先记录相应的undo log,把被更新的列的旧值记录下来,生成undo log语句,写入buffer pool的undo界面,后续的undo log更改都需要记录到redo log buffer里
- 开启更新,先更新内存(标记为脏页),再把记录写到redo log buffer里,这个时候更新就完成了。后续会选择一个合适的时机进行脏页刷盘,刷盘时会先刷redo log buffer里的数据再刷脏页的数据。
- 一条更新语句执行完后,此时记录的binlog会保存到binlog cache里并没有刷新到硬盘上的binlog文件里,在事务提交时才会将所有的binlog刷新到硬盘。
- 事务提交
- prepare:将redo log状态设为prepare,并将redo log刷新到硬盘里(这个redo log写入磁盘的是数据的变更情况)
- commit:将binlog刷新到磁盘,接着将redo log状态设置为commit(这个redo log写入磁盘的是事务的状态标记prepare -> commit)
- undo log:InnoDB存储引擎层的逻辑日志,保证了事务的原子性,用于事务回滚和MVCC
- redo log:InnoDB存储引擎层的物理日志,保证了事务的持久性,用于掉电等故障的数据恢复
- bin log:Server层的逻辑日志,用于主从复制和数据备份
逻辑日志和物理日志的区别
- 逻辑日志:描述的逻辑操作,记录的逻辑语句,恢复的时候需要重放逻辑。结构紧凑。
- 物理日志:记录的数据的变化例如 对 XXX 表空间中的 YYY 数据页 ZZZ 偏移量的地方做了AAA 更新,占用的空间较大,但是数据恢复的速度快
Redo log
包含两部分一部分是InnoDB存储引擎层的redo log buffer另一部分是磁盘里的redo log file
- 让事务有了持久性,让MySQL有了崩溃恢复的能力能够保证MySQL在任何时间崩溃,都能够恢复已提交的数据
- WAL实现了让随机写变成了顺序写(写入redo log是顺序写),提高了IO的效率
- 具体实现:写操作不立刻写到磁盘上,而是先写到日志上,然后再在合适的时间写到磁盘上
- undo log页面被修改后要把修改列的旧值写入buffer Pool里的undo页面,同时也要把相关的语句写入redo log里进行持久化存储
undo log
- 让事务有了原子性
- 能够实现MVCC
bin log
- 数据恢复和主从复制
redo log 和 undo log 的使用场景
- 事务提交之前发生崩溃(不是宕机崩溃而是事务出现问题)重启后会根据undo log回滚事务。
- 事务提交之后发生崩溃(宕机崩溃)重启后根据redo log恢复事务,不会造成数据丢失。
redo log和bin log的使用场景
- redo log文件是循环写,边写边擦,只记录未被刷入磁盘的物理地址,已经刷入磁盘的数据会从redo log中擦除。redo log是存储引擎层的日志能够知道脏页刷没刷盘,从而在崩溃恢复的时候恢复那些没被刷盘的脏页数据。
- binlog 保存的全量的日志,保存了所有数据的变更,只要记录在binlog上的数据都可以恢复,binlog是server层的日志不能知道哪写脏页还没有刷盘。
binlog 两阶段提交过程(为了redo log和binlog的日志一致性,开启了内部XA事务)
- prepare阶段:将XID写入redo log里,redo log刷入磁盘
- commit阶段:将XID写入binlog里,binlog刷入磁盘,将redo log状态设置为commit
两个日志出现数据不一致的节点
- 当MySQL重启的时候会按顺序扫描redo log找到XID后会拿着XID去binlog里去找
- redo log刷入了磁盘,而还没有将XID写入binlog里。
- 这个时候redo log里有XID完成了刷盘而binlog里没有XID,发生了数据不一致,进行回滚事务。
- binlog刷入磁盘,binlog的状态还没设置为commit。
- 这个时候redo log里有XID完成刷盘并且binlog里也有XID完成刷盘,进行事务提交。
- 所以对于处于prepare状态的redo log来说既可以进行提交事务也可以进行回滚事务,主要取决于binlog里有没有XID,也就是binlog的写成功为事务成功的标识。
- redo log刷入了磁盘,而还没有将XID写入binlog里。
学习MVCC
MVCC是为了当多个事务读写数据库时能够保持数据的一致性和隔离性,它是通过维护多个版本的数据来实现的,当事务对数据进行修改时,MVCC会创建个数据快照,而不是直接修改数据行。RC是每个select语句都会生成一个新的Read View,而RR是只会在第一个select执行时生成Read View。RC下,MVCC可以解决脏读。RR隔离等级下,MVCC解决了不可重复读和部分幻读。
一致性非锁定读:快照读 -> 读取数据行的历史数据
- 普通的
select语句
锁定读(对读取的记录加锁):当前读 -> 读取数据行的最新数据
select ... lock in share modeselect ... for updateupdate,delete,insert都属于当前读
RR隔离级别下如果执行的是快照读,那用MVCC就能避免幻读(select以后生成一个Read View并且后面的读取都用这个),而如果是当前读,那MVCC就解决不了,要用(记录锁+间隙锁)临键锁(Next-Key Lock)来锁定一个范围内的数据,防止其他事务在间隙间插入数据。
- 不可重复读:一个事务前后的查询中发现数据进行了修改或减少
- 幻读:一个事务前后的查询中发现数据进行了新增
- 幻读可以看作是一种特殊的不可重复读,那为什么要单拎出来呢,因为对于这两种情况的处理手段不同,对于
delete和update操作可以直接对记录加锁保证事务安全,而对于insert操作需要单加记录锁只能锁住已存在的记录,需要额外用到间隙锁住一个范围,这样也就能控制一个范围不允许插入新值了。
但是RR下的幻读还是没有完全得到解决
- T1时刻:A事务执行快照读,读出四条数据
- T2时刻:B事务插入一条数据
- T3时刻:A事务执行当前读,那就会发生幻读,读出四条数据
MVCC的三个关键知识点:隐藏字段,Read View,undo log
MVCC通过隐藏字段的DB_TRX_ID与Read View来判断数据的可见性,如果不可见那就通过数据行的DB_ROLL_PTR去找到可见的历史版本,每个事务读到的数据版本可能不一致,在同一个事务中用户只能看到这个事务创建Read View之前已经提交的修改和该事务本身做的修改。
隐藏字段
- DB_TRX_ID:更新当前数据行的事务ID
- DB_ROLL_PTR:指向当前数据行的undo log的历史版本
- DB_ROW_ID:如果没有主键,那就使用此id作为主键
ReadView
- m_low_limit_id:目前出现过的最大的事务ID+1,大于等于这个的都不可见
- m_up_limit_id:活跃事务列表中最小的事务ID,小于这个的都可见
- m_ids:创建Read View时其他未提交的活跃事务的ID列表,不包括当前事务自己和已提交的事务
- m_creator_trx_id:创建该Read View的事务ID
undo log
- 作用:
- 用于数据的回滚恢复
- 当读取记录时,如果该记录被其他事务占用或当前版本对该事务不可见,那就可以通过undo log读取历史版本
- 分类:
- insert undo log
- update undo log
RC下的MVCC在进行快照读时的提取数据的过程
RR下的MVCC在进行快照读时的提取数据的过程
MVCC + 锁 = 隔离性