innodb是 mysql 的当前默认存储引擎。该引擎支持外键、行级锁定和 acid 事务。这些功能使 innodb 成为现代应用程序的可靠且合适的选择。它的崩溃恢复机制、数据完整性和高性能是 innodb 目前成为默认 mysql 引擎的一些原因。
更新语句在mysql中是如何执行的
假设有一条如下这样的sql语句,那么这条语句是如何执行的呢?
update users set name = 'xxx' where id = 1;
首先java系统会通过一个数据库连接将该sql语句发送到mysql上,然后经过sql接口、查询解析器、查询优化器、执行器环节,在解析出了sql语句、生成了执行计划后,再由执行器调用innodb存储引擎的接口去执行生成的执行计划。
下面介绍innodb存储引擎里的架构设计,以及如何基于innodb存储引擎完成一条更新语句的执行。
重要的内存结构—buffer pool缓冲池
innodb有一个非常重要、放在内存里的组件,就是缓冲池(buffer pool)。缓冲池会缓存很多磁盘文件数据,以便在查询时不用去查磁盘。如下图示:
所以当innodb存储引擎要执行更新语句时:比如对"id=1"这一行数据,会先判断"id=1"这一行数据是否在缓冲池里。如果不在,则直接从磁盘里加载到缓冲池里,且对这行记录加独占锁。
undo日志文件如何让更新的数据可以回滚
假设"id=1"这行数据的name原来是"zhangsan",现在要更新为"xxx"。那么innodb得先把原值"zhangsan"和"id=1"写入到undo日志文件中。
java系统在执行一条sql更新语句时,要是它在一个事务里,那么事务提交前是可以对数据进行回滚的。所以考虑到可能要回滚数据,innodb会把更新前的值写入undo日志文件。如下图示:
更新buffer pool缓冲池中的缓存数据
当innodb把要更新的那行记录从磁盘文件加载到了缓冲池,同时对它加完锁,而且还把更新前的旧值写入undo日志文件后,innodb就可以正式开始更新这行记录了。
更新的时候,会先更新缓冲池中的记录,此时这个数据就是脏数据了。所谓的更新内存缓冲池里的数据,意思就是把内存里的"id=1"这行数据的name字段修改为"xxx"。
为什么说此时这行数据是脏数据呢?因为这时磁盘上"id=1"这行数据的name字段还是"zhangsan",但内存里这行数据已经被修改了,所以它是脏数据。
redo log buffer如何避免宕机时数据丢失
现在已经把内存里的数据进行了修改,但是磁盘上的数据还没修改。此时万一mysql所在机器宕机,必然会导致内存里已修改的数据丢失。这该如何处理?
为此必须把对内存所做的修改写入一个redo log buffer里,redo log buffer也是内存里的一个缓冲区,是用来存放redo日志的。
所谓redo日志,就是记录innodb要对数据做什么修改。比如对"id=1"这行记录修改name字段的值为"xxx",就是一条redo日志。
如果还没提交事务时mysql宕机了怎么办
在数据库中,哪怕执行一条sql语句,其实也可以是一个独立的事务。只有当事务提交后,sql语句才算执行结束。
所以如果还没提交事务mysql宕机了,那么必然导致内存里buffer pool中修改过的数据都丢失,同时写入redo log buffer中的日志也会丢失。
此时数据丢失其实是不要紧的。因为一条更新语句只要没提交事务,那么就代表还没执行成功。此时mysql宕机虽然导致内存里的数据丢失,但还没影响磁盘上的数据。
提交事务时将redo日志写入磁盘中
如果innodb想要提交一个事务,就会根据一定的策略把redo日志从redo log buffer中刷入到磁盘文件里,这个策略是通过如下这个参数来配置的:innodb_flush_log_at_trx_commit。
(1)当innodb_flush_log_at_trx_commit = 0时
那么进行事务提交时,不会把redo log buffer的数据刷入到磁盘文件里。这时即便提交了事务,但如果mysql宕机了,内存里的数据也会全部丢失而且redo日志里没有数据。
(2)当innodb_flush_log_at_trx_commit = 1时
那么进行事务提交时,会把内存中的redo log刷入到磁盘文件里。只要事务提交成功,那么redo log就必然在磁盘里。哪怕此时buffer pool中更新过的数据还没刷新到磁盘,系统崩溃重启后,也可以根据磁盘中的redo log恢复。
(3)当innodb_flush_log_at_trx_commit = 2时
那么进行事务提交时,会把内存中的redo log写入到os cache缓存里。os cache缓存里的数据可能在1秒后才会被写入到磁盘文件中。
在这种模式下,当innodb存储引擎提交事务后,redo log可能还停留在os cache缓存里,还没实际进入到磁盘文件。而此时mysql所在机器宕机了,那么os cache里的redo log也会丢失。从而出现即便提交了事务,但是数据还是丢失了的情况。
redo日志刷盘策略的选择和建议
通常建议设置innodb_flush_log_at_trx_commit的值为1。也就是提交事务时,redo日志必须同时刷入磁盘文件里。这样可以严格保证提交事务后数据绝对不会丢失。
如果innodb_flush_log_at_trx_commit = 0,那么提交事务后如果mysql宕机而此时redo日志还没有刷盘,则会导致内存里的redo日志丢失,内存更新好的数据也丢失。
如果innodb_flush_log_at_trx_commit = 2,那么提交事务后虽然redo日志进入了os cache,但os cache的数据此时还没进入磁盘文件而mysql机器宕机了,则也会导致os cache的redo日志丢失。
所以一般设置redo日志刷盘策略为1,保证事务提交后数据不会丢失。
mysql的redo log和binlog对比
mysql的redo log,是一种偏向物理性的重做日志。因为其记录的是:对哪个数据页中的哪条记录做了什么修改。而且redo log是属于innodb存储引擎特有的日志文件。
mysql的binlog,是一种偏向于逻辑性的日志,也叫归档日志。类似"对users表中id=1的一行记录做了更新操作,更新后的值是什么"。binlog不是innodb存储引擎特有的日志文件,binlog是属于mysql数据库层面的日志文件。
提交事务时同时也会写入binlog
提交事务时,除了会把redo日志写入到磁盘文件中,还会把这次sql更新对应的binlog日志写入到磁盘文件中。
下图加入了执行器这个组件,它会负责和innodb存储引擎进行交互:
步骤1:从磁盘加载数据到buffer pool缓存
步骤2:写入undo日志
步骤3:更新buffer pool里的数据
步骤4:写入redo日志到redo log buffer
步骤5:redo日志刷入磁盘
步骤6:写入binlog日志
实际上,执行器是非常核心的一个组件。执行器会与存储引擎完成sql语句在磁盘与内存层面的全部数据更新操作。
下图把一次更新语句的执行,拆分为两个阶段。其中步骤1、2、3、4是执行更新语句的阶段,而步骤5和6是属于提交事务的阶段。
binlog日志的刷盘策略分析
binlog日志也有不同的刷盘策略,通过sync_binlog参数可以控制binlog的刷盘策略,默认值是0。
(1)当sync_binlog设置为0时
表示执行器没有直接将binlog写入磁盘文件,而是先将binlog写入os cache缓存,与redo log的innodb_flush_log_at_trx_commit的值为2一样。
如果os cache里的数据还没写入磁盘文件时,mysql所在机器宕机,那么binlog日志也会丢失。
(2)当sync_binlog设置为1时
表示在提交事务时,执行器会把binlog直接写入到磁盘文件中。这样在提交事务后即便宕机,binlog也不会丢失。
基于binlog的redo log完成事务的提交
当mysql把binlog写入磁盘后,接着就会完成最终的事务提交。此时会把本次更新对应的binlog文件名称和位置,都写入到redo日志里,同时在redo日志文件里写入一个commit标记。在完成这个事情后,才算是最终完成事务的提交。
在redo日志中写入commit标记的意义
写入commit标记是用来保持redo日志与binlog日志一致。也就是说,在提交事务的时候,上图的步骤5、6、7必须都执行完毕,才算是提交了事务。
(1)如果刚完成步骤5时,redo日志刚刷入到磁盘文件,mysql宕机了
这时因为在redo日志没有最终的事务commit标记,所以此次事务不成功。因为不允许出现这样的情况:redo日志文件里有更新日志,但是binlog日志文件里没有对应的更新日志。否则就会导致数据不一致。
(2)如果在完成步骤6时,binlog日志已写入磁盘,mysql宕机了
这时因为在redo日志没有最终的事务commit标记,所以此次事务也失败。所以必须要在redo日志写入最终的事务commit标记,才算事务提交成功。这样redo日志有本次更新的日志,binlog日志也有本次更新的日志,从而实现redo日志和binlog日志完全一致。
后台io线程随机将内存更新后的脏数据刷盘
当完成事务提交后,mysql已把内存中的buffer pool缓存数据更新了,同时磁盘里也有redo日志和binlog日志,但磁盘上的数据文件还是旧值。
这时mysql会有一个后台io线程,在事务提交后的某个时间,随机把内存buffer pool中修改后的脏数据刷回到磁盘上的数据文件里。
当io线程把buffer pool里修改后的脏数据刷回磁盘后,磁盘上的数据才会跟内存里的数据一样,都是修改后的值。
当io线程把脏数据刷回磁盘之前,即便mysql宕机也没关系。因为重启后会根据redo日志恢复提交事务时所做的修改到内存里。之后io线程还是会把修改后的数据刷到磁盘的数据文件里。
innodb存储引擎的架构原理总结
innodb存储引擎会使用buffer pool、redo log buffer来缓存数据。innodb存储引擎有属于自己的undo日志文件、redo日志文件,mysql也有属于自己的binlog日志文件。
执行更新时:会修改buffer pool里的数据、写undo日志、写redo log buffer。
提交事务时:会把binlog刷入磁盘、在redo日志中写入事务标记,把redo日志刷入磁盘。最后innodb后台的io线程会随机把buffer pool的脏数据刷入到磁盘文件。
(1)mysql宕机重启如何确定是否需要从redo日志恢复数据
mysql宕机重启,如何确定脏数据在宕机前是否已全部刷写回磁盘文件。
mysql宕机重启,innodb会首先去查看数据页中lsn的数值。lsn就是innodb使用的一个版本标记的计数。如果数据页中的lsn异于redo日志的commit标记,那么就去查看redo日志的lsn大小。如果数据页的lsn值大,则说明数据页领先redo日志,不需要恢复,反之则需要从redo日志中恢复。
(2)从redo日志恢复数据时是全量恢复还是指定位置后恢复
redo日志是划归于一个redo日志组的。默认一个redo日志组有两个redo日志文件。写redo日志时是循环写入,写满一个redo日志文件再写另外一个。
在写满切换redo日志文件时,会触发数据库的检查点checkpoint。checkpoint所做的事就是把脏页刷新回磁盘。
当db重启恢复时只需要恢复checkpoint之后的数据即可。所以redo日志文件大小不宜过大,不然导致恢复时需要更长的时间。redo日志文件大小也不宜过小,不然导致频繁切换触发检测点降低性能。
(3)既然有redo日志来保证崩溃恢复,为什么还要有binlog日志
binlog日志其实就是归档日志,主要用来做数据恢复的。mysql最开始设计时只有myisam引擎只有binlog,不支持innodb。此外数据库备份以及hadoop系统数据分析都是binlog来实现的,所以还需要binlog。
(4)redo日志和binlog日志的数据结构是怎样的
redo日志是循环写,会把redo日志分为0,1,2,3四个区间,有两个指针。writepos指针是一边写一边向后移动,checkpoint指针是一边擦除一边向后移动。所以redo日志是不能保存很多记录的,必须持久化到磁盘中。binlog日志是追加写,不会覆盖之前的日志。
(5)binlog日志和redo日志是怎么保持一致性的
binlog日志和redo日志是通过两阶段提交来保持一致性的。否则如果数据库系统发生crash,则通过redo日志恢复的数据库和通过binlog日志恢复出来的临时库不一致。
总结
到此这篇关于mysql存储引擎innodb架构原理和执行流程的文章就介绍到这了,更多相关mysql中innodb架构原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!