一条SQL查询语句是如何执行的?
架构介绍
MySQL的基本架构图如下:
MySQL分为Server层和存储引擎层两部分。
Server层包括连接器、查询缓存、分析器、优化器、执行器,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(比如日期、时间、数学和加密函数等),还有所有的跨存储引擎的功能都在这层实现(比如存储过程、触发器、视图等)
存储引擎层负责数据的存储和提取,是插件式的架构模式,支持InnoDB、MyISAM、Memory等多个存储引擎。自从MySQL5.5.5版本后,InnoDB成为了默认存储引擎。
执行过程
1.连接器
第一步先连接到数据库上,连接器负责跟客户端建立连接、获取权限、维持和管理连接。
连接命令如下:
mysql -h$ip -P$port -u$user -p
连接的时候,如果用户名密码认证通过,连接器会到权限表里查到拥有的权限。之后,这个连接里的权限判断逻辑,都依赖此刻查询到的权限。
这也意味着,一个用户成功建立连接后,如果用管理员账号对该用户的权限做了修改,也不会影响到已经存在连接的权限。修改完成后,只有新建的连接才会使用新的权限设置。
连接完成后,如果没有后续动作,该连接就处于空闲状态。客户端如果太久没有操作,连接器就会自动将它断开,这个时间由参数wait_timeout控制,默认8小时。连接被断开以后,客户端再次发送请求的话,就会收到一个错误提醒,提示进行重连。
建立连接的过程一般比较复杂,尽量使用长连接。
全部使用长连接后,MySQL占用内存涨的特别快,因为MySQL在执行过程里,临时使用的内存都是管理在连接对象里的,只有在连接断开时才会被释放,所以长期累积下来,容易OOM。
解决这个问题的方案:
- 定期断开长连接
- MySQL5.7+版本,通过执行
mysql_reset_connection
来重新初始化连接资源。
2. 查询缓存
之前执行过的语句和结果以k-v键值对的形式被直接缓存在内存里,如果查询能够在缓存里找到这个key,value就会直接被返回给客户端。
如果语句不在查询缓存里,会执行后面的阶段。执行结束后,结果被存入查询缓存。
查询缓存的失效:只要有对一个表的更新,该表所有的查询缓存就会被清空。对于更新频繁的数据库来说,查询缓存的命中率非常低;只有很久时间才更新一次的静态表,才适合使用查询缓存。
参数设置: query_cache_type=DEMAND
表示默认不使用查询缓存。如果确定要使用的话,需要使用SQL_CACHE
显式指定,比如select SQL_CACHE * from T where ID=10;
版本更新: MySQL8.0 后没有查询缓存这个功能了
3. 分析器
先对SQL语句做解析。
分析器会先做"词法分析",需要识别出里面的字符串分别是什么、代表什么,同时,分析器会判断表是否存在、字段是否存在。然后做“语法分析”,根据词法分析的结果,语法分析器根据语法规则,判断输入的SQL语句是否满足语法。
4. 优化器
优化器式表里有多个索引的时候,决定使用哪个索引。或者在多表关联(join
)的时候,决定各个表的连接顺序。
5. 执行器
开始执行语句的时候,先判断对这个表有没有执行查询的权限,没有的话显示没有权限的错误。有权限的话,打开表继续执行。打开表的时候,执行器会根据表的引擎定义,去使用该引擎提供的接口。
比如下面的查询语句
select * from T where ID=10;
如果表T里ID字段没有索引,那么执行流程是这样的:
- 调用InnoDB引擎接口取这个表的第一行,判断ID值是否为10,如果不是的话就跳过,否则就存到结果集里
- 调用引擎接口取下一行,重复相同的判断逻辑,直到取到这个表的最后一行
- 执行器将上述过程里满足条件的所有行组成的结果集返回给客户端
如果有索引的话,第一次调用的是“满足条件的第一行”接口,之后循环取“满足条件的下一行”接口。
数据库的的慢查询日志里有rows_examined
字段,表示这个语句执行过程中扫描了多少行。这个值是执行器每次调用引擎获取数据行的时候累加的。
但是有些场景下,执行器调用一次,引擎内部扫描多行,因此引擎扫描行数和rows_examined
字段并不是完全相同。