一、假设要执行的SQL语句如下
update users set name = 'aaa' where id = 5;
把users表中字段id等于5的数据行的name字段的值修改为aaa,那么这条SQL语句是如何执行的呢?
从应用服务器和MySQL服务器的角度看,首先是应用服务器中的一个业务模块需要更新数据库数据,先通过一个数据库连接将SQL语句发送到MySQL服务器上,然后经过SQL接口、解析器、优化器、执行器几个环节,解析SQL语句,生成执行计划,执行器根据这个执行计划去调用存储引擎的接口去执行语句。
这个过程看上一篇
二、在存储引擎中,这条更新语句是如何执行的
(存储引擎有很多种,此处只记录InnoDb存储引擎)
执行update users set name = 'aaa' where id = 5;
步骤说明:
一、将包含id=5的数据页加载进Buffer Pool中
Buffer Pool:是InnoDB存储引擎中非常重要的组件,就是一个缓冲池,会缓存一些从磁盘加载进来的数据页,在查询的时候,如果是缓冲池里已经加载的数据,就不需要去查磁盘文件了,查询内存的效率比查询磁盘文件的效率要高的多。对数据的增删改查其实都是基于buffer pool的。
所以在执行update语句的时候,首先是要先找到id=5的这行数据,因此先在缓冲池里查找,如果缓冲池中不存在的话,就会从磁盘文件加载到缓冲池。
二、将旧数据写入undo log日志文件
假设这条数据原本的值name=‘xx’,现在要修改为‘aaa’,这里要先把旧值‘xx’写入到undo日志文件中去。目的是为了事务的回滚和支持InnoDB的MVVC机制。
事务的回滚:InnoDB存储引擎是支持事务的,在事务提交前,是可以对修改了的数据进行回滚的,就是把更新的'aaa'值回滚成之前的‘xxx’,所以为了回滚数据的需要,这里要记录旧值。
MVVC机制:多版本并发控制。是InnoDB存储引擎的一种处理并发读写的机制,其中redo log版本链是重要基础。这里先不赘述,后面记录到数据库的并发时再详细写。
三、更新Buffer Pool中的缓存数据
在第一步中已经把id=5的这行数据加载到Buffer Pool中了,这个时候这行数据的name字段值还是‘xx’,要将其修改为新值‘aaa’。
此时,这行数据就成了脏数据,即这个时候,在Buffer Pool中的数据与在磁盘的数据是不一致的,磁盘的数据还是旧的‘xx’。
四、写redo log buffer
redo log buffer:是内存中的一个缓冲区,记录的是你对这行数据的操作。此时在redo log buffer中记录,对id=5的这行数据修改了name字段的值为‘aaa’。
redo log buffer是用来在MySQL突然宕机的时候,用来恢复更新过的数据的。因为若MySQL数据库宕机,Buffer Pool中缓存的数据会丢失,此时磁盘文件还未更新,那么之前的修改就丢失了。此时只需要重新执行redo log buffer中记录过的操作,就可以恢复出宕机前的Buffer Pool。
但是redo log buffer也是在InnoDb存储引擎内存中的,若MySQL宕机,则redo log buffer数据也是会丢失的,因此redo log buffer这个记录是要进行持久化的,继续看下一步。
五、提交事务的时候将redo log日志写入os cache或者磁盘
为什么是提交事务的时候写而不是其它的时候呢?假设这条update操作的事务还未提交,此时MySQL宕机了,会怎么样?
Buffer Pool缓冲池的数据丢失,同时redo log buffer中记录的操作也会丢失,但是此时数据丢失并不重要,因为事务还未提交,没提交的事务就代表没执行成功,磁盘上的数据还是原来旧的数据‘xx’,此时不会有任何影响。
如果事务提交了,就表示这个执行成功了。事务有四大特性,ACID,即原子性、一致性、隔离性和持久性。其中持久性就是说,一旦事务提交,则其所做的修改就要永久保存到数据库中,此时即使系统崩溃,修改过的数据也不会丢失。
如果要事务提交,则应该将redo日志从redo log buffer中刷入到磁盘文件里。
这里InnoDB提供了几种策略,这个策略是通过参数 innodb_flush_log_at_trx_commit来配置的。
值为0,表示提交事务的时候,不会把redo log buffer里的数据刷入到磁盘文件中。此时可能事务提交成功,但是MySQL宕机,导致内存中的数据和redo日志都丢失了。这是不符合事务的持久性的。
值为1,表示提交事务的时候,把redo log从内存刷入到数据库的磁盘文件中去。此时只要事务提交成功,那么redo log就必然在磁盘文件里了,这个数据的修改也是不会丢失的。哪怕此时MySQL系统宕机,Buffer Pool中的数据还未刷新到磁盘中,磁盘中还是修改前的旧值‘xx’,只要MySQL重启,就可以根据redo日志文件去恢复之前做过的修改,重做一次redo buffer,然后在合适的时机将脏数据刷回磁盘文件。
值为2,表示提交事务的时候,把redo log buffer中的数据写入磁盘文件对应的os cache缓存里去,而不是直接写入磁盘文件,可能1秒后才会把os cache里的数据写入磁盘文件。os cache是系统缓存,这种情况下,如果MySQL服务器宕机,则数据不会丢失,但是如果机器宕机,那么os cache的数据就会丢失了,因此同样会导致数据丢失。
这三种redo日志刷盘策略根据系统的特点和需要进行选择,一般是设置为1,即提交事务的时候,redo日志必须刷入到磁盘文件里,这样可以严格的保证,提交事务之后,数据绝对不会丢失,因为有redo 日志在磁盘文件里可以恢复出buffer pool。
六、将Buffer Pool缓冲池中的数据刷回磁盘
数据库中的数据最终都是要存放到磁盘文件中去的,但是为了执行效率,在执行SQL语句的时候,是不可能直接将数据的修改写入磁盘文件的,因此才有了redo日志。
使用redo 日志,存储引擎在修改数据的时候,只需要修改这行数据在Buffer Pool中的值,即这个数据的内存拷贝,然后将这个行为记录到redo日志文件中,记录redo日志是采用追加的方式,即在磁盘上一小块区域内的顺序IO,这个操作是很快的。
而真实的数据在数据库磁盘文件中,分布磁盘中不同的位置,在写数据的时候,磁头需要在磁盘的多个位置移动,属于随机IO,这个过程比顺序IO效率低很多,速度相当慢,随便一个大磁盘文件的随机读写操作都可能要几百毫秒。
因此,会有一个IO线程在后台运行,在数据库空闲的时候或者Buffer Pool缓冲页回收的时候,将数据慢慢写回到数据库磁盘文件中去。