文章目录
问题 1):一条简单的Select 是如何执行的?
问题 2):一条简单的update 是如何执行的?
要回答一条语句是如何进行update的,就需了解 buffer pool
、 binlog
、redo
、undo
了。
名词解释之:数据页、Buffer Pool、以及各种日志
1)页是什么?
是不是我们需要的数据多大,我们就一次从磁盘加载多少数据到内存呢?
磁盘 I/O 的读写相对于内存的操作来说是很慢的。如果我们需要的数据分散在磁盘的不同的地方,那就意味着会产生很多次的 I/O 操作。
所以,无论是操作系统也好,还是存储引擎也好,都有一个预读取的概念。也就是说,当磁盘上的一块数据被读取的时候,很有可能它附近的位置也会马上被读取到,这个就叫做局部性原理。那么这样,我们干脆每次多读取一点,而不是用多少读多少。
类似于操作系统的缓冲行
,可以参阅这两篇文档补票
InnoDB 设定了一个存储引擎从磁盘读取数据到内存的最小的单位,叫做页
。
操作系统也有页的概念,操作系统的页大小一般是 4K。而在 InnoDB 里面,这个最小的单位默 认是 16KB
大小,它是一个逻辑单位。
如果要修改这个值的大小,必须修改源码重新编译安装。
2)buffer pool 是什么?
设想一下:
如果对于数据页的操作,每次都直接操作磁盘,从磁盘加载到内存,这样会不会很慢?能不能把这些页缓存呢?
InnoDB 使用了一种缓冲池的技术,也就是把磁盘读到的页
放到一块内存区域里面,这个内存区域就叫 Buffer Pool。
下一次读取相同的页,先判断是不是在缓冲池里面,如果是,就直接读取,不用再次访问磁盘。
- 修改数据的时候,先修改缓冲池里面的页。
- 内存的数据页和磁盘数据不一致的时候,我们把它叫做
脏页
。 - InnoDB 里面有专门的后台线程把 Buffer Pool 的数据写入到磁盘,每隔一段时间就一次性地把多个修改写入磁盘,这个动作就叫做
刷脏
。
3)redo log 是什么?
思考一个问题:
如果 Buffer Pool 里面的脏页还没有刷入磁盘时,数据库宕机或者重启,这些数据丢失。如果写操作写到一半,甚至可能会破坏数据文件导致数据库不可用。
为了避免这个问题,InnoDB 把所有对页面的修改操作专门写入一个日志文件,并且在数据库启动时从这个文件进行恢复操作(实现 crash-safe),要说明白crash-safe 又需要先说明什么是redo log 。
这个日志文件就是磁盘的 redo log(叫做重做日志,同时也是用它来实现事务的持久性)。
redo Log如其名侧重于重做!它记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页,且只能恢复到最后一次提交的位置。
redo log用到了WAL(Write-Ahead Logging)
技术,这个技术的核心就在于修改记录前,一定要先写日志,并保证日志先落盘,才能算事务提交完成。
有了redo log再修改数据时,InnoDB引擎会把更新记录先写在redo log中,在修改Buffer Pool中的数据,当提交事务时,调用fsync
把redo log刷入磁盘。
至于缓存中更新的数据文件何时刷入磁盘,则由后台线程异步处理。
注意
:此时redo log的事务状态是prepare,还未真正提交成功,要等bin log日志写入磁盘完成才会变更为commit,事务才算真正提交完成。
这样一来即使刷脏页之前MySQL意外宕机也没关系,只要在重启时解析redo log中的更改记录进行重放,重新刷盘即可
问题:
同样是写磁盘,为什么不直接写到 db file 里面去?为什么先写日志再写磁盘?
因为写dbFile是随机 I/O ,而写redoLog是顺序 I/O 的概念。
redo log 有什么特点?
- 1、
redo log 是 InnoDB 存储引擎实现的
,并不是所有存储引擎都有。支持崩溃恢复是 InnoDB 的一个特性。 - 2、
redo log 是**物理日志**
,记录的是“在某个数据页上做了什么修改”。 - 3、
redo log 的大小是固定的
,前面的内容会被覆盖,一旦写满,就会触发 buffer pool 到磁盘的同步,以便腾出空间记录后面的修改。
write pos
表示redo log当前记录的日志序列号LSN(log sequence number),写入还未刷盘,循环往后递增;
check point
表示redo log中的修改记录已刷入磁盘后的LSN,循环往后递增,这个LSN之前的数据已经全落盘。
-
write pos
到check point
之间的部分是redo log空余的部分(绿色),用来记录新的日志; -
check point
到write pos
之间是redo log已经记录的数据页修改数据,此时数据页还未刷回磁盘的部分。 -
当
write pos
追上check point
时,会先推动check point向前移动,空出位置(刷盘)再记录新的日志。
注意:redo log日志满了,在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求的,此刻MySQL的性能会下降。所以在并发量大的情况下,合理调整redo log的文件大小非常重要。
redo log 的内容主要是用于崩溃恢复。磁盘的数据文件,数据来自 buffer pool(只有 redo log 写满了,不能再记录更多内存的数据了,才把 buffer pool 刷盘,然后覆盖 redo log)。
4)crash-safe 又是如何做到的?
因为redo log的存在使得Innodb引擎具有了crash-safe的能力,即MySQL宕机重启,系统会自动去检查redo log,将修改还未写入磁盘的数据从redo log恢复到MySQL中。
MySQL启动时,不管上次是正常关闭还是异常关闭,总是会进行恢复操作。会先检查数据页中的LSN,如果这个 LSN 小于 redo log 中的LSN,即write pos位置,说明在redo log上记录着数据页上尚未完成的操作,接着就会从最近的一个check point出发,开始同步数据。
简单理解,比如:redo log的LSN是500,数据页的LSN是300,表明重启前有部分数据未完全刷入到磁盘中,那么系统则将redo log中LSN序号300到500的记录进行重放刷盘。
5) undo log 有什么特点?
除了 redo log 之外,还有一个跟修改有关的日志,叫做 undo log。(redo log 和 undo log 与事务密切相关,统称为事务日志。)
在执行 undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,属于逻辑格式的日志。
记录的是数据修改前的状态,在数据修改的流程中,同时会记录一条与当前操作相反的逻辑日志到undo log中。
我们举个栗子:假如更新ID=1记录的name字段,name原始数据为小富,现改name为程序员内点事
事务执行update X set name = 程序员内点事 where id =1语句时,先会在undo log中记录一条相反逻辑的update X set name = 小富 where id =1记录,这样当某些原因导致服务异常事务失败,就可以借助undo log将数据回滚到事务执行前的状态,保证事务的完整性。
那可能有人会问:同一个事物内的一条记录被多次修改,那是不是每次都要把数据修改前的状态都写入undo log呢?
答案是不会的!
undo log只负责记录事务开始前要修改数据的原始版本,当我们再次对这行数据进行修改,所产生的修改记录会写入到redo log
,undo log负责完成回滚
,redo log
负责完成前滚
。
6) 回滚
未提交的事务,即事务未执行commit。但该事务内修改的脏页中,可能有一部分脏块已经刷盘。如果此时数据库实例宕机重启,就需要用回滚来将先前那部分已经刷盘的脏块从磁盘上撤销。
7) 前滚
未完全提交的事务,即事务已经执行commit,但该事务内修改的脏页中只有一部分数据被刷盘,另外一部分还在buffer pool缓存上,如果此时数据库实例宕机重启,就需要用前滚来完成未完全提交的事务。将先前那部分由于宕机在内存上的未来得及刷盘数据,从redo log中恢复出来并刷入磁盘。
数据库实例恢复时,先做前滚,后做回滚。
8) 小结
如果你仔细看过了上边的 MySQL数据更新流程图 就会发现,undo log、redo log、bin log三种日志都是在刷脏页之前就已经刷到磁盘了的,相互协作最大限度保证了用户提交的数据不丢失。
Binlog
binlog 以事件的形式记录了所有的 DDL 和 DML 语句(因为它记录的是操作而不是数据值,属于逻辑日志),可以用来做主从复制和数据恢复。
跟 redo log 不一样,它的文件内容是可以追加的,没有固定大小限制。
在开启了 binlog 功能的情况下,我们可以把 binlog 导出成 SQL 语句,把所有的操作重放一遍,来实现数据的恢复。
binlog 的另一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器的 binlog,然后执行一遍。