1、更新语句经过解析与优化,生成执行计划,交由执行器调用存储引擎接口
执行器会多次调用存储引擎接口,并不是一次完成
2、查询旧值,如果这个值不在内存缓冲区则需要查找旧值,从磁盘数据页加载到内存缓冲区
3、先将旧值写入undo log日志文件中,用于回滚数据
4、更新内存中的数据
5、向更新操作写入内存中redo log buffer中
6、redo log buffer里的日志每隔一秒会自动刷新到磁盘
将redo log buffer中跟本事务相关的redo log日志刷新到磁盘,并向磁盘redo log文件中写入一个prepare redo log
7、准备提交事务,将binlog日志写入磁盘binlog文件中(binlog的写入逻辑:在事务执行过程中,先把日志写到binlog cache,事务提交的时候再把binlog cache写到binlog文件中,并清空bin log cache)
8、把本次更新对应的binlog文件名称和这些更新的binlog日志在磁盘文件的位置都写入到redo log日志文件中,同时在redo log 文件中写入一个commit标志
9、事务处理完成
10、mysql 后台线程会负责将数据刷入到磁盘
MySQL中的WAL技术:先写日志,再将数据更新到磁盘。
1、redo log 和 binlog
MySQL整体来看分为Server层和引擎层,Server层主要做MySQL功能层面的事情,引擎层负责存储具体的相关事宜。redo log是InnoDB引擎特有的日志,而Server层也有自己的日志称为binlog。
这两种日志有以下三点不同:
1、redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
2、redo log是物理日志,记录的是“在某个数据页上做了什么修改”,binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这行的c字段加1”。
3、redo log是循环写的,空间固定会用完;binlog是可以追加写入的,写到一定大小会切换到下一个,并不会覆盖以前的日志。
redo log:
write pos是当前记录的位置(最新的更新记录的位置,chekpoint到write pos是需要更新到磁盘的记录),一边写一边往后移,写到第3号文件末尾后就回到0号文件开头。
checkpoint是当前要擦除的位置(表示这条记录以及之后的记录还未将更新的内存数据写到磁盘),也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
从write pos 到 checkpoint之间的部分,可以用来记录新的操作,如果check point表示不能再执行新的更新,得停下来擦除一些记录把checkpoint推进一下。
redo 日志缓冲刷新到磁盘中的redo 日志文件的时机
- Master Thread 每一秒将redo 日志缓冲刷新到redo 日志文件
- 每个事务提交时会将redo 日志缓冲刷新到redo 日志文件
- 当redo 日志缓冲剩余空间小于1 / 2时,redo 日志缓冲刷新到redo 日志文件
2、两阶段提交
将redo log日志写入拆成了两个步骤:prepare和commit。
为什么要有两阶段提交?
redo log和binlog都可以用于表示事务提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
如果没有两阶段提交?这里的写redo log是从redo log buffer写到磁盘的redo log文件。
以字段值c从0更新为1为例:
1、先写redo log后写binlog:假设在redo log写完,binlog还没写完的之后,MySQL进程一场重启。redo log写完之后,系统即使崩溃(系统崩溃时内存的值还未更新到磁盘),仍然能把数据c恢复为1(redo log记录了数据变化之后的值,实现成功执行update的效果)。但由于binlog没写完就崩溃了,binlog没有这个记录, 恢复临时库的话,由于这个语句的binlog丢失,临时库就会少了这一次更新,恢复出来的数据还是0,数据并未更新。
2、先写binlog后写redo log:如果在binlog之后崩溃,由于redo log还没写,崩溃恢复以后redolog中没有commit,所以认为事务无效,这一行的c的值还是0。但binlog以及记录了“c从0变为1”这个日志,所以之后用binlog恢复时候多出一个事务出来,恢复c的值为1。
可以看到如果不使用两阶段提交,那么数据库的状态就有可能和用它的日志恢复出来的库状态不一致。
3、更新数据时不同时刻崩溃的处理
单独执行一个更新语句时,InnoDB会自己启动一个事物,在语句执行完成的时候提交。
如果在时刻A出现MySQL重启,也就是写入redo log处于prepare阶段后,写binlog之前发生了崩溃。由于此时binlog还没写,redo log还没提交,所以崩溃恢复的时候,这个事务会回滚(撤销这次事务的更新记录),binlog也还没写,所以两个日志的状态信息是一样的。
如果在时刻B出现MySQL重启,也就是binlog写完,redo log还没commit前发生崩溃,那么MySQL如何处理?
- 如果redo log里面的事务是完整的,也就是有了commit标识,则直接提交。
- 如果redo log里面的事物只有完整的prepare,则判断对应的事务binlog是否存在并完整:
- 如果是,则提交事务、
- 否则,回滚事务
MySQL如何知道binlog是完整的?
一个事务的binlog是有完整格式的
- statement格式的binlog,最后会有COMMIT
- row格式的binlog,最后会有一个XID event
4、redo log 和 binlog是怎么关联的?
它们有一个共同的是数据字段XID。崩溃回复的时候,会按顺序扫描redo log:
如果碰到既有prepare,又有commit的redo log,就直接提交
如果碰到只有prepare,而又没有commit的redo log,就拿着XID去binlog找对应的事务
5、能否只用redo log
redo log 这样的历史日志没法保留,也就起不到归档的作用。