前言
SQL 语句的最终执行者是存储引擎。存储引擎在经解析器、优化器处理后被执行器调用其接口执行优化后的执行计划。MySQL 存储引擎包括 InnoDB、Myisam、Memory、Archive、CSV 存储引擎等,其中最常用也是MySQL 默认的存储引擎是 InnoDB。
写入缓冲池(Buffer pool)
InnoDB 中有一个非常重要的组件——缓冲池(Buffer pool),缓冲池中存储了 MySQL 在磁盘文件上的数据缓存。所以当 InnoDB 执行更新语句时,会首先判断数据是否在缓冲池,如果不在,那么会从磁盘中加载到缓冲池,然后对更新的记录加独占锁,再执行更新。
写入undo 日志文件
更新数据会有更新失败的可能,那如果更新失败需要回滚怎么办?此时就需要在更新之前把需要更新的记录写入 undo 日志文件,在提交日志之前都是可以回滚的。
更新数据
在把数据写入缓冲池、原来的数据写入 undo 之后,就可以正式更新数据了。这时首先更新的是加入缓冲池的缓存数据,更新之后原来的 “name=zhangsan” 就变成了 “name=lisi” ,缓存中的数据就是脏数据了,因为此时磁盘上还是 “name=zhangsan” ,但是内存中的数据已经修改了。
写入redo log buffer
经过以上步骤,内存中的缓存数据已经修改了,如果此时数据库宕机了,那刚才修改的岂不是毫无意义了?所以这个时候就需要把对缓存做的修改写入到 redo log buffer 中,这个缓冲区是用于存放 redo 日志的,就是记录对数据的修改操作。所以当数据库宕机时,就可以通过 redo 日志来恢复对数据的操作。那如果事务提交之前,数据库宕机了呢?内存中的数据会全部丢失,但是磁盘上的数据并没有修改,而且事务并没有提交成功,所以并没有影响。
在提交事务时,会根据策略把 redo 日志写入磁盘文件,这个策略通过 innodb_flush_log_at_trx_commit 来配置。
策略一:参数值为 0 时,不会把 redo 日志写入磁盘,如果事务提交成功后宕机了,那么内存中的数据和 redo 日志都会丢失,这种方式性能最高,但是对事务而言不安全;
策略二:参数值为 1 时,事务成功时,必须把 redo 日志写入磁盘,只要事务成功,redo 日志必然在磁盘中,这种方式是最安全的事务,但是会损耗性能;
策略三:参数为 2 时,把 redo 日志写入 os cache 系统内存中,而不是直接写入磁盘,在一定时间后才会由系统刷入磁盘,这种方式也有风险,写入系统内存后宕机依然会丢失数据,事务却提交成功了,但是性能相比策略二低,安全性相比策略一高,算是一种折中方式吧。
一般建议 redo 日志刷盘策略选策略二,事务一旦提交成功,数据绝不能丢失。
写入binlog
实际上MySQL 在提交事务成功时,不仅会 redo 刷盘还会写入 binlog 日志。redo 是 InnoDB 存储引擎所特有的,binlog 属于 MySQL Server 的日志文件。binlog 是一种归档日志,记录的是偏向逻辑性的日志,类似于“对user表中的id=10这行数据做了更新操作,更新后的值是lisi”;而 redo 是一种偏向物理性值的重做日志,类似于“对哪个数据页中的什么记录,做了什么修改”。步骤1、2、3、4属于事务开始提交,5、6属于事务提交。
binlog 也有不同的刷盘策略,使用参数 sync_binlog 控制 binlog 的刷盘策略。策略一:参数值为 0,此时会先写入 os cache,然后等待 os cache 刷盘,所以这种策略也是有安全风险的,可能会丢失 binlog 日志;策略二:参数值为 1,此时会在提交事务时,强制写入磁盘。
提交事务
binlog 写入磁盘后,紧接着会完成最终的事务提交,此时会把本次更新对应的 binlog 文件名称和在 binlog 文件中的位置都写入 redo log 日志文件中,同时在 redo log 日志文件写入 commit 标记。完成后就算完成了事务的提交。
写入 commit 标记是用来保持 redo log 和 binlog 日志保持一致,即写入 commit 标记,表示 redo log 和 binlog 日志文件中都存在本次更新的日志了。
刷盘
MySQL 后台会有一个线程在合适的时机把修改的缓存刷回磁盘。