在上一篇文章中,我们已经讲解了MySQL查询语句的执行过程。有兴趣的可以去看:面试官:MySQL是如何执行查询语句的?在这篇文章中,我们来谈谈MySQL更新语句的执行原理。更新过程和查询过程有什么区别?基本流程也是一样的,就是说也是经过parser和optimizer的处理,最后交给executor。区别在于得到合格数据后的操作。养成好习惯,先上三连,码字不易,谢谢大家的点赞关注收藏。BufferPoolBufferPool首先,InnnoDB的数据是存放在磁盘上的。存储引擎要对数据进行操作,首先要将数据从磁盘加载到内存中。这里有个问题,是不是我们需要多少数据,一次从磁盘加载多少数据到内存呢?与内存操作相比,磁盘I/O读取和写入非常慢。如果我们需要的数据分散在磁盘的不同地方,意味着会产生很多的I/O操作。所以不管是操作系统的文件管理系统,还是存储引擎,都有一个预读的概念。也就是说,当磁盘上的一条数据被读取时,很可能它附近的位置也会被立即读取。这称为局部性原理。所以这样一来,我们只是每次多读一点,而不是用多少就读多少。我们为存储引擎设置一个最小单位,将数据从磁盘读取到内存,称为页面。操作系统也有页的概念。操作系统的页大小一般为4K,而在InnoDB中,最小单位默认为16KB,是一个逻辑单位。我们要操作的数据就在这样一个页面中,数据所在的页面称为数据页。对于数据页的操作,我们不会每次都直接操作磁盘,因为磁盘的速度太慢了。使用了缓冲池技术,即将从磁盘读取的页面放在一个内存区域中。下次读取同一页时,先判断是否在该内存区,如果是则直接读取,不再访问磁盘。该内存区域称为缓冲池。接下来我们看一下bufferpool在整个mysql架构中的位置,有个宏观的认识。修改数据时,先修改缓冲池(BufferPool)中的页。当内存中的数据页与磁盘数据不一致时,我们称之为脏页。InnoDB有专门的后台线程将BufferPool的数据写入磁盘,每隔一段时间将多个修改一次性写入磁盘。此操作称为弄脏。InnoDB内存结构上图中可以看到很多文件(redolog、binlog、undolog),下面我们来分析一下。Redolog想到一个问题:如果BufferPool中的脏页还没有刷到磁盘,数据库宕机或者重启,数据就会丢失。为了避免这个问题,InnoDB将所有的页面修改操作写入一个日志文件,在数据库启动时从这个文件中恢复(实现crash-safe)——用它来实现事务持久化。这个文件是磁盘的重做日志(称为redolog),对应/var/lib/mysql/目录下的ib_logfile0和ib_logfile1,各48M。这种日志和磁盘配合的整个过程其实就是MySQL中的WAL技术(Write-AheadLogging)。它的关键点是先写日志,再写磁盘。问题:也是写磁盘,为什么不直接写db文件呢?为什么先写入日志再写入磁盘?我们先来了解一下随机I/O和顺序I/O的概念。如果我们需要的数据随机散布在磁盘上不同页的不同扇区,那么要找到对应的数据,需要等到磁臂旋转到指定页,然后磁盘找到对应的扇区才能找到什么我们需要。一条数据,这个过程一次性执行,直到找到所有数据,这是随机IO,读取数据速度慢。假设我们已经找到了第一条数据,其他需要的数据都在这条数据的后面,那么就不需要重新寻址,可以依次得到我们需要的数据。这称为顺序IO。Diskflushing是随机I/O,而logging是顺序I/O(顺序写入),顺序I/O效率更高。因此,先将修改写入日志文件,在保证内存数据安全的情况下,可以延迟刷盘的时机,从而提高系统吞吐量。这个重做日志有什么特点呢?Redolog是由InnoDB存储引擎实现的,并不是所有的存储引擎都有。支持崩溃恢复是InnoDB的一个特性。不是记录数据页更新后的状态,而是记录这个页发生了什么变化,属于物理日志。redolog的大小是固定的,之前的内容会被覆盖。一旦满了,就会触发redolog同步到磁盘,为后续的修改腾出空间。除了redolog,还有一个和修改相关的日志,叫做undolog。Redolog和undolog与事务密切相关,统称为事务日志。undologundolog(undologorrollbacklog)记录事务前的数据状态(不包括select)。如果修改数据时出现异常,可以使用undolog实现回滚操作(保持原子性)。undo在执行时,只是逻辑上将数据恢复到事务前的状态,而不是对物理页进行操作,属于日志的逻辑格式。undolog的数据默认在系统表空间ibdata1文件中,因为共享表空间不会自动收缩,也可以单独创建一个undo表空间。有了这些日志,我们总结一下一条mysqlupdate语句的执行流程,就是一个简化的过程。name的原值为mayun;updateusersetname='jiangwang'whereid=1;启动事务,从内存或磁盘中获取这些数据,并将其返回给服务器的执行器;执行者将这行数据的值修改为jiangwang;recordname=mayun撤销日志;recordname=jiangwangtoredolog;调用存储引擎接口,修改内存中的name=jiangwang(BufferPool);事务提交。在内存和磁盘之间,有很多后台线程在工作。什么是后台线程?后台线程的主要作用是刷新内存池中的数据,将修改后的数据页刷新到磁盘中。后台线程分为:master线程、IO线程、purge线程、pagecleaner线程。Binlog除了InnoDB架构中的日志文件,MySQL的Server层还有一个日志文件叫binlog,所有存储引擎都可以使用。Binlog以事件的形式记录所有的DDL和DML语句(因为它记录的是操作而不是数据值,属于逻辑日志),可用于主从复制和数据恢复。与重做日志不同的是,它的文件内容可以追加,没有固定的大小限制。开启binlog功能后,我们可以将binlog导出为SQL语句,replay所有操作,实现数据恢复。binlog的另一个功能是实现主从复制。它的原理是从服务器读取master服务器的binlog,重新执行。有了这两条日志,我们再看看一条update语句是如何执行的(redo不能一下子写):比如一条语句:updateusersetname='LittleHorse'whereid=1;首先查询这里如果有缓存,也会使用缓存。改名为Pony,然后调用引擎的API接口,将这行数据写入内存,同时记录redolog。此时redolog进入prepare状态,然后告诉executor执行完成,可以随时提交。executor收到通知后记录binlog,然后调用存储引擎接口将redolog设置为commit状态。更新完成。总结一下MySQL的update语句执行过程的原理,上面已经说的很清楚了。最后总结一下重点:先记录到内存(缓冲池),再写入日志文件。记录重做日志分为两个阶段(准备和提交)。存储引擎和服务器分别记录不同的日志。先记录redo,再记录binlog。
