mysql中SELECT语句的执行流程与优化

12次阅读

mysql执行select语句的典型流程是:连接→查询缓存(8.0已移除)→解析→预处理→优化器→执行器→存储引擎层→返回结果,关键性能环节在优化器和存储引擎层。

mysql中SELECT语句的执行流程与优化

SELECT 语句到底经过了哪些阶段?

MySQL 执行一条 SELECT 语句,不是直接去磁盘查数据,而是走一套有明确顺序的内部流程。理解这个流程,才能知道优化该从哪下手。

典型流程(以 InnoDB 为例)是:连接 → 查询缓存(8.0 已移除)→ 解析 → 预处理 → 优化器 → 执行器 → 存储引擎层 → 返回结果。其中真正影响性能的关键环节集中在「优化器」和「存储引擎层」。

  • MySQL 5.7 及以前:查询缓存可能成为性能陷阱——只要表有任意更新,整个表相关缓存全失效;8.0 直接删了 query_cache_type,不用再纠结是否开启
  • EXPLaiN 输出的 type 字段(如 ALLrangeref)反映的是优化器选择的访问类型,不是 SQL 写得“对不对”,而是“它认为怎么最快”
  • 执行器不负责数据读取,只调用存储引擎接口(如 ha_innobase::index_read()),真正的 I/O、加锁、MVCC 判断都在 InnoDB 层完成

为什么加了索引,EXPLAIN 还显示 type=ALL?

常见误解是“建了索引就一定走”,其实优化器会基于统计信息、索引选择性、WHERE 条件写法、隐式类型转换等综合判断。哪怕索引存在,也可能被跳过。

  • 对索引字段做函数操作:WHERE YEAR(create_time) = 2023 → 无法使用 create_time 上的普通 B+ 树索引
  • 隐式类型转换WHERE user_id = '123'user_idint)→ MySQL 转成数字比较,但可能导致索引失效(尤其在字符集不同或 collation 不一致时)
  • 联合索引未满足最左前缀:INDEX (a, b, c),但查询是 WHERE b = 2 AND c = 3 → 完全用不上该索引
  • 优化器估算全表扫描比走索引更快(比如查 80% 的行)→ 它真会选 ALL,这不是 bug,是成本计算结果

ORDER BY 和 LIMIT 怎么避免 filesort?

EXPLAINExtra 列出现 using filesort,说明 MySQL 需要额外排序缓冲区,不是靠索引顺序直接返回结果。这在大数据量下极易拖慢响应。

  • 如果 ORDER BY 字段在索引中且顺序匹配,就能复用索引有序性。例如 INDEX (status, created_at),则 WHERE status = 1 ORDER BY created_at DESC 可免排序
  • ORDER BY a ASC, b DESC 在 MySQL 8.0+ 才支持混合方向索引(INDEX (a ASC, b DESC)),低版本只认统一方向
  • LIMIT 本身不触发 filesort,但和 ORDER BY 组合时,若没索引支撑,MySQL 仍需先排完所有匹配行再截断——所以 LIMIT 10 并不能让 ORDER BY 变快
  • 覆盖索引能进一步减少回表:比如 SELECT id, name FROM users WHERE status=1 ORDER BY created_at,可建 INDEX (status, created_at, id, name)

JOIN 为什么慢?驱动表选错是主因

MySQL 的 JOIN 是嵌套循环(Nested Loop),没有真正意义上的哈希连接(8.0.18+ 才在特定条件下用 Hash Join,但默认仍倾向 NLJ)。谁当驱动表(outer table),决定了外层循环次数和整体代价。

  • 优化器通常选「预估结果集更小」的表为驱动表。但统计信息不准(ANALYZE TABLE 没及时运行)、或条件写法干扰判断(如子查询、OR 条件),会导致选错
  • 强制指定驱动表可用 STRAIGHT_JOIN,但属高危操作——必须确认被驱动表有合适索引,否则性能雪崩
  • 小表驱动大表 ≠ 数据量小的表,而是「WHERE 条件过滤后结果集小」的表。比如用户表 100 万行,但 WHERE is_vip = 1 只返回 10 行,则它是理想驱动表
  • 关联字段类型/字符集不一致(如 utf8mb4_general_ci vs utf8mb4_0900_as_cs)会导致无法使用索引,JOIN 变成全表交叉
SELECT STRAIGHT_JOIN u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id WHERE u.is_vip = 1 AND o.status = 'paid';

真正难的不是写出快 SQL,而是看懂 EXPLAIN 里每个字段在说什么,以及明白 InnoDB 怎么把你的 SQL 翻译成页读取、行锁、间隙锁这些底层动作。很多“优化”失败,是因为在执行器层调优,而瓶颈其实在存储引擎的 Buffer Pool 命中率或 MVCC 版本链遍历上。

text=ZqhQzanResources