vlambda博客
学习文章列表

一条sql语句在Mysql中是如何执行的


我们的应用程序可以与Mysql Server建立长连接,从而可以通过该网络连接将sql发送给它,然后Mysql Server会将该sql的执行结果响应给我们,说明其实Mysql Server和我们的普通的web应用程序没什么两样,都是有SocketServer监听某一个端口,接受用户的请求然后响应,而今天我们关注的重点是当Mysql Server接收到我们的sql之后,是如何一步步获取相应结果的?

简明扼要地说,Mysql Server接收到sql之后要做的第一件事就是调用sql解析器来理解这个sql属于增删还是改查,操作的是table A还是table B,使用了什么where条件,需不需要排序等;接下来就轮到查询优化器了,条条大路通罗马,但有的路遥远且崎岖,有的路邻近且平坦,它会帮助我们计算出执行这个sql的最优路径并生成一个执行计划;最后执行器拿到这个执行计划会一步步地调用存储引擎接口来获取响应的结果,而我们今天要说的是InnoDB存储引擎接收到执行器的执行指令后是如何执行的?

InnoDB的架构

这是mysql官网上的InnoDB架构图,分为内存架构和磁盘架构,内存架构中有:Buffer Pool、Change Buffer、Log Buffer、Adaptive Hash Index(自适应Hash索引),磁盘架构中主要就是系统表空间,表的ibd文件,回滚日志和重放日志,内存和磁盘之间通过操作系统缓存联系起来。

内存架构

1、Buffer Pool

Buffer Pool是用来缓存表和索引的一块内存区域,使得经常被访问的数据可以从内存直接获取,差不多80%的物理内存空间都用作Buffer Pool,它的主要组成部分是由page组成的一个链表,内存空间是一定的,当里面放的数据越来越多,自然会淘汰一部分最近不被经常访问的数据(least recently used,也称为lru),和传统我们了解的lru算法不同,mysql的lru算法略显复杂:

从官方提供的这张图片来看,该链表一共被分为两部分:5/8的链表称为New Sublist,3/8的链表被称为Old Sublist,它们相遇的地方称为MidPoint,该图片的右侧有两个自上而下和自下而上的箭头,说的是经常被访问的数据会慢慢的被移动到New Sublist的头部,不经常被访问的数据会慢慢的被移动到Old Sublist的尾部直至淘汰。为什么要将该链表一分为二呢?试想,假如你执行了一个没有where条件的查询语句,从磁盘加载出来了大量的page该放在哪里呢,放在链表的头部吗?如果如此大量的数据今后不被访问,岂不是浪费的了宝贵的缓存空间,所以就暂且将它放在Old Sublist的头部,如果以后它被使用到了,就慢慢晋升,如果没被使用到,就逐渐被淘汰。但是,除了这些特殊的sql语句,正常情况下从磁盘加载的page还是会被放在New Sublist的头部的,据我所知,假如该链表的前10个数据页非常hot,经常被访问到,是不是InnoDB就会不断的移动它们的位置?显然不是,它自己会设置一个阈值,用来控制链表的前多少个元素不会被经常移动,因为经常移动比较浪费cpu。

2、Change Buffer & Log Buffer

Change Buffer是Buffer Pool的一部分,它的存在主要是为了缓存记录的变更,而不是直接读磁盘物理更新,这样更新会非常快。一方面,当下次该记录被从磁盘加载到内存时再物理更新,另一方面,会有一个后台线程会选择一个合适的时间执行更新。

而Log Buffer就是undo log和redo log的缓冲区。

3、Adaptive Hash Index

我们知道,哈希算法查找的时间复杂度为O(1),非常之快。自适应哈希索引其实是InnoDB自优化的,我们不必介入。当我们在B+树中查询数据的时候,磁盘IO的次数取决于树的高度,在我们反复查询数据的过程中,InnoDB会建立一个它自认为可以提高查询效率的哈希索引,使得查询有的数据不必多次磁盘IO,构建哈希索引的速度非常快,因为它构建的哈希索引并不针对于整张表,而是表的部分数据。

磁盘架构

其实磁盘架构之前已经说了很多了,无非就是系统表空间,表的元数据frm文件,表的数据及索引ibd文件,这里又看到了个undo log, redo log分别是用于数据回滚和数据回放。

总结

现在让我们回过头来看看,当我们执行update t_user_account set account = 1000 where id = 10这条sql语句是如何执行的:

  • sql解析器理解该sql是一个更新语句,table为t_user_account,要更新的列为1000,where条件为id = 10

  • 查询优化器生成执行计划

  • 执行器调用InnoDB存储引擎执行执行计划

  • InnoDB在Change Buffer中对该行记录加独占锁

  • 记录undo log

  • 在Change Buffer中记录该行的更新

  • 记录redo log

  • 提交事务,根据落盘策略决定是否落盘。