这里主要讲解存储引擎的部分,server层可以看看这篇文章
第1步:会先去看缓冲区有没有这条数据,如果有就不进行缓存,直接进入第三步。
第2步:会将要修改的那一行数据所在的一整页加载到缓冲池Buffer Pool中
第3步:将旧值写入undo日志中,便于回滚以及mvcc机制的运作
第4步:将Buffer Pool中的数据更新为新的数据。
第5步:写入redo日志缓冲池,redo日志的内容实际和binlog差不多,但是作用不同(写入redo日志是磁盘顺序写,mysql会先预留一块区域给redo日志使用,速度会比随机读要更快)
第6步:准备提交事务前,将redo日志写入磁盘
第7步:准备提交日志前,将binlog日志写入磁盘
第8步:将commit标记写到redo日志中,事务提交完成。该操作时为了保证事务提交后redo和binlog数据一致性
第八步:将缓冲区的数据写入磁盘,注意这里的写入不是及时写入的,而是随机的。
说一下Buffer pool中写入磁盘的过程
这里并不是直接一次性将缓冲区中的数据写入磁盘的,而是分两步:两次写给 InnoDB 带来的是可靠性,主要用来解决 部分写失败(partial page write)。doublewrite 有两部分组成,一部分是内存中的 doublewrite buffer ,大小为 2M,另外一部分就是物理磁盘上的共享表空间中 连续的 128 个页,即两个区,大小同样为 2M。当缓冲池的作业刷新时,并不 直接写硬盘,而是通过 memcpy 函数将脏页先拷贝到内存中的 doublewrite buffer,之后通过 doublewrite buffer 再分两次写,每次写入 1M 到共享表空间 的物理磁盘上,然后马上调用 fsync 函数,同步磁盘
doublewrite buffer 和 redo日志起的是不同的作用,redo只适用于数据完全没有更新,例如还没有开始写入磁盘的时候就断电了。而doublewrite 则适用于写入了一半之后断电了。两个都是为了保证数据的可靠性
为什么innodb不选择直接将数据更新到磁盘,而是先写入缓冲区并且引入一系列操作呢?
如果一条sql进来就直接写入磁盘,那这种写入就数据随机读写磁盘,效率是非常低的,而通过缓冲区一次读取一页数据,接下来的sql操作的行如果也在缓冲区中,那就先修改缓冲区,而修改缓冲区的速度是非常非常快的,然后到了某个时机在将所有的sql进行写入磁盘,这无形减少了磁盘io。当然,在写入之前肯定会在缓冲区里对数据进行了处理。而引入一系列操作是为了保证数据库的高可用性,undo日志用于事务的回滚以及mvcc机制,这是innodb的特性。而redo日志则保证了出现断电等极端现象重启数据库时数据能够恢复等
什么是两阶段提交?为什么要有两阶段提交?
两阶段提交:将redo日志的提交分为prepare和commit两个阶段,称之为两阶段提交。第五第六步为第一阶段,第七步标记commit为第二阶段
没有两阶段提交的情况
1.当只有redo log,binlog失效时,会导致主库可以通过redo log来重做,而从库因为没有及时获取到binlog而不能进行回放,导致主从数据不一致。这样会出现 redo log 写入到磁盘了,但是 binlog 还没写入磁盘,于是当发生 crash recovery 时,恢复后,主库会应用redo log,恢复数据,但是由于没有 binlog,从库就不会同步这些数据,主库比从库“新”,造成主从不一致
2.跟上一种情况类似,很容易知道,这样会反过来,造成从库比主库“新”,也会造成主从不一致。
两阶段提交,解决crash recovery 问题
如果 redo log 已经 commit,那毫不犹豫的,把事务提交
如果 redo log 处于 prepare,则去判断事务对应的 binlog 是不是完整的
- 如果是,则把事务提交
- 如果不是,则事务回滚
两阶段提交,其实是为了保证 redo log 和 binlog 的逻辑一致性。
redo日志
位置:
位于data目录下的ib_logfile0
可以通过sql查询 show variables like '%innodb_log_group_home_dir%';
作用:
用于数据库宕机的时候,重启数据库时恢复宕机期间已经写入了redo日志但来不及写入磁盘idb文件的数据
redo log 写入磁盘的过程分析:
redo log 从头开始写,写完一个文件继续写另一个文件,写到最后一个文件末尾就又回到第一个文件开头循环写,如下面这个图所示。
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。
checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件里。
write pos 和 checkpoint 之间的部分就是空着的可写部分,可以用来记录新的操作。如果 write pos 追上checkpoint,表示redo log写满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下,再擦除掉之前,如果当前的内容还没有写入磁盘idb文件,那么myslq就会强制将修改的数据内容写入磁盘idb文件。
事实上第一张主导图中的第五步,redo日志写入磁盘文件之间还有一步是先将数据写入os缓存(os page cache)中,然后再写入redo log磁盘文件。
![](https://img-blog.csdnimg.cn/ce5d45c3b44c4aa3a526fb5ae7e01d22.png)
binlog日志
作用
可以通过sql查看是否开启:show variables like '%log_bin%';
配置binlog日志
# log‐bin设置binlog的存放位置,可以是绝对路径,也可以是相对路径,这里写的相对路径,则binlog文件默认会放在 data数据目录下
log‐bin=mysql‐binlog
# Server Id是数据库服务器id,随便写一个数都可以,这个id用来在mysql集群环境中标记唯一mysql服务器,集群环 境中每台mysql服务器的id不能一样,不加启动会报错
server‐id=1
# 其他配置
binlog_format = row # 日志文件格式,下面会详细解释
expire_logs_days = 15 # 执行自动删除binlog日志文件的天数, 默认为0, 表示不自动删除
max_binlog_size = 200M # 单个binlog日志文件的大小限制,默认为 1GB
![](https://img-blog.csdnimg.cn/8a1cac7c6b3e4c10860907b109ee3b50.png)
查看binlog相关参数信息
用参数 binlog_format 可以设置binlog日志的记录格式,mysql支持三种格式类型:
- STATEMENT:基于SQL语句的复制,每一条会修改数据的sql都会记录到master机器的bin-log中,这种方式日志量小,节约IO开销,提高性能,但是对于一些执行过程中才能确定结果的函数,比如UUID()、 SYSDATE()等函数如果随sql同步到slave机器去执行,则结果跟master机器执行的不一样。
- ROW:基于行的复制,日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改记录下每一行数据修改的细节,可以解决函数、存储过程等在slave机器的复制问题,但这种方式日志量较大,性能不如Statement。举个例子,假设update语句更新10行数据,Statement方式就记录这条update语句,Row方式会记录被修改的10行数据。
- MIXED:混合模式复制,实际就是前两种模式的结合,在Mixed模式下,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种,如果sql里有函数或一些在执行时才知道结果的情况,会选择Row,其它情况选择Statement,推荐使用这一种。
重新生成binlog日志条件:
- 服务器启动或重新启动
- 服务器刷新日志,执行命令flush logs
- 日志文件大小达到 max_binlog_size 值,默认值为 1GB
redo日志和binlog日志的区别
这两种日志都是用于数据恢复的,redo日志主要是用于系统突然宕机的时候,进行数据恢复,而binlog日志是用于正常情况下数据不小心被误删,需要数据恢复的情况