执行一条 `SELECT` 语句的详细过程

3 阅读4分钟

一、前言

  • 本文详细介绍了 MySQL 执行一条 SELECT 查询语句的内部流程。
  • 通过理解这些流程,可以更好地了解 MySQL 的架构和工作原理。

二、MySQL 执行流程概览

MySQL 的架构分为两层:

  1. Server 层:负责建立连接、分析和执行 SQL。

    • 包括连接器、查询缓存、解析器、预处理器、优化器、执行器等模块。
    • 实现了所有内置函数和跨存储引擎的功能(如存储过程、触发器、视图等)。
  2. 存储引擎层:负责数据的存储和提取。

    • 支持多种存储引擎(如 InnoDB、MyISAM、Memory 等)。
    • 不同存储引擎支持的索引类型不同,例如 InnoDB 使用 B+ 树索引。

三、执行 SELECT 语句的详细步骤

第一步:连接器

  • 建立连接

    • 客户端通过 TCP 三次握手连接到 MySQL 服务。

    • 使用命令 mysql -h[ip] -u[user] -p 连接到 MySQL 服务。

      # -h 指定 MySQL 服务的 IP 地址(本地可省略)
      # -u 指定用户名
      # -p 指定密码(建议不在命令行中直接写密码)
      mysql -h[ip] -u[user] -p
      
  • 身份验证

    • 连接器会验证用户名和密码。
    • 如果验证失败,返回错误信息并断开连接。
    • 如果验证成功,连接器会获取该用户的权限,并保存起来。
  • 连接管理

    • 可以通过 SHOW PROCESSLIST 查看当前连接的客户端信息。

    • 空闲连接会根据 wait_timeout 参数自动断开(默认 8 小时)。

      SHOW VARIABLES LIKE 'wait_timeout';
      
    • 可以手动断开空闲连接:

      KILL CONNECTION [id];
      
    • MySQL 支持的最大连接数由 max_connections 参数控制。

      SHOW VARIABLES LIKE 'max_connections';
      

第二步:查询缓存(MySQL 8.0 已删除)

  • 查询缓存机制

    • 查询缓存以 key-value 形式保存在内存中,key 是 SQL 查询语句,value 是查询结果。
    • 如果查询语句命中查询缓存,直接返回结果。
    • 如果未命中,则继续后续流程,并将结果存入查询缓存。
  • 查询缓存的局限性

    • 对于更新频繁的表,查询缓存的命中率很低。
    • 表的任何更新操作都会清空该表的查询缓存。
    • MySQL 8.0 版本移除了查询缓存模块。

第三步:解析 SQL

  • 解析器的作用

    • 对 SQL 语句进行词法分析和语法分析。

    • 词法分析:识别关键字(如 SELECTFROM)和非关键字(如表名、字段名)。

    • 语法分析:根据语法规则判断 SQL 是否合法,并构建语法树。

      -- 示例 SQL
      SELECT username FROM userinfo;
      -- 词法分析结果:
      -- 关键字:SELECT, FROM
      -- 非关键字:username, userinfo
      
    • 注意:解析器只负责语法检查,不会检查表或字段是否存在。

第四步:执行 SQL

执行 SQL 查询语句分为三个阶段:

  1. 预处理阶段

    • 检查表或字段是否存在。
    • SELECT * 中的 * 符号扩展为表上的所有列。
    -- 示例:表不存在的情况
    SELECT * FROM test;
    -- 错误信息:ERROR 1146 (42S02): Table 'mysql.test' doesn't exist
    
  2. 优化阶段

    • 优化器根据查询成本选择最佳的执行计划。

    • 例如,选择使用哪个索引(如果有多个索引)。

    • 可以通过 EXPLAIN 查看执行计划:

      EXPLAIN SELECT id FROM product WHERE id > 1 AND name LIKE 'i%';
      
  3. 执行阶段

    • 执行器根据执行计划从存储引擎读取记录,并返回给客户端。

    • 执行器与存储引擎的交互过程如下:

      • 主键索引查询

        SELECT * FROM product WHERE id = 1;
        
        • 执行器调用存储引擎的接口,定位到主键索引的记录。
        • 存储引擎返回记录给执行器,执行器判断是否符合查询条件。
      • 全表扫描

        SELECT * FROM product WHERE name = 'iphone';
        
        • 执行器调用存储引擎的全表扫描接口。
        • 存储引擎逐条返回记录,执行器判断是否符合查询条件。
      • 索引下推(Index Condition Pushdown, ICP)

        SELECT * FROM t_user WHERE age > 20 AND reward = 100000;
        
        • 优化器将部分条件下推到存储引擎层,减少回表操作。
        • 存储引擎在索引中过滤满足条件的记录后,再执行回表操作。

总结

执行一条 SELECT 查询语句的完整流程包括:

  1. 连接器:建立连接、管理连接、校验用户身份。

  2. 查询缓存:检查查询缓存(MySQL 8.0 已移除)。

  3. 解析 SQL:进行词法分析和语法分析,构建语法树。

  4. 执行 SQL

    • 预处理:检查表或字段是否存在,扩展 SELECT *
    • 优化:选择最佳执行计划。
    • 执行:从存储引擎读取记录并返回结果。

参考资料