SQL SELECT 语句执行过程

SQL Select 语句整个查询执行过程,总的来说分为 6 个步骤 :
- 连接:客户端向 MySQL 发送一条查询请求
- 客户端与服务器建立连接:通过TCP/IP、Unix套接字或命名管道等方式
- 身份验证:验证用户名、密码和主机权限
- 连接管理:服务器为每个连接分配线程,通过
SHOW PROCESSLIST
可查看 - 会话变量设置:根据配置文件或用户请求初始化会话环境
与 MySQL 建立的连接,是由连接器 Connectors 来完成的
客户端如果长时间没有和 MySQL 产生交互,连接器就会自动将此次连接断开;这个时间是由参数 wait_timeout 控制的,默认值是8小时。如果连接被断开之后,客户端若再次发送请求会收到一个错误 的提醒:Lost connection to MySQL server during query
客户端与 MySQL 建立连接的过程是比较复杂繁琐的,建议在使用中要尽量减少建立连接的动作,尽量 使用长连接。但是若全部使用长连接,有时候会导致 MySQL 的占用内存飞速飙升,这是因为 MySQL 在 执行过程中临时使用的内存是管理在连接对象里面的。这些资源只有在连接断开的时候才释放。所以如果 长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了
长连接和短链接:
- 长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接
- 短连接是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个
- 缓存:服务器会先检查查询缓存,如果命中缓存,则直接返回缓存结果,否则进入下一阶段
- 缓存查找:MySQL 将 SELECT 语句作为 key 在查询缓存中查找
- 缓存条件:要求 SQL 语句完全一致(包括空格、大小写)
- 缓存失效:表数据修改会导致相关缓存全部失效
- 缓存配置:通过
query_cache_type
和query_cache_size
参数控制 - 注意:MySQL 8.0已移除查询缓存功能,因维护缓存开销常大于收益
- 解析:服务器进行 SQL 解析(词法语法)、预处理
- 词法分析:将 SQL 语句分解为令牌(tokens),识别关键字、表名、列名等
- 语法分析:检查 SQL 语法是否正确,构建解析树
- 语义检查:验证表、列是否存在,用户是否有权限
- 视图展开:如果查询涉及视图,将视图替换为定义
- 预处理:处理表达式,进行常量折叠等优化
- 优化:再由优化器生成对应的执行计划
- 查询重写:优化器可能重写查询以提高效率
- 执行计划生成:
- 选择使用哪些索引
- 决定表的连接顺序(对于多表查询)
- 评估全表扫描与索引扫描的成本
- 考虑使用临时表或文件排序
- 统计信息使用:基于表的统计信息(行数、索引基数等)做出决策
- 优化器提示:用户可通过
FORCE INDEX
等提示影响优化器决策
- 执行:MySQL 根据执行计划,调用存储引擎的 API 来执行查询
- 执行引擎:调用存储引擎 API 获取数据
- 索引使用:根据执行计划使用相应的索引
- 表扫描:如果需要,执行全表扫描
- 连接操作:执行嵌套循环连接、哈希连接或排序合并连接
- 排序分组:执行 ORDER BY、GROUP BY 等操作
- 临时表:必要时创建临时表处理中间结果
- 结果:将结果返回给客户端,同时缓存查询结果
- 结果集生成:将数据组装成客户端需要的格式
- 网络传输:通过连接将结果发送给客户端
- 增量返回:对于大结果集,可能分批返回
- 缓存写入:如果查询缓存开启且查询可缓存,将结果存入缓存
- 资源清理:释放执行过程中使用的临时资源
SQL Update 语句执行过程
更新语句在执行时也会先走一遍同样的查询语句的流程
MySQL 整个更新的执行过程,总的来说分为 5 个步骤 :
执行的更新 SQL:update t_student set age=age+1 where id=2
- 客户端向 MySQL 服务器发送一条更新请求
- 客户端与MySQL服务器建立连接后发送 UPDATE 语句
- 服务器接收 SQL 文本并进行初步校验(如连接权限检查)
- 如果是事务中操作,会检查当前事务状态(活跃/已提交/已回滚)
- 清除表查询缓存 ,跟这个有关的查询缓存会失效。这就是一般不建议使用查询缓存的原因
- 立即失效化:标记与该表相关的所有查询缓存条目为无效
- 缓存维护开销:这是查询缓存的主要缺点之一,任何修改操作都会导致相关缓存失效
- 性能影响:对于写频繁的应用,维护缓存的开销可能超过收益
- 注意:MySQL 8.0+已完全移除查询缓存功能
- 分析器进行 SQL 解析(词法和语法分析),分析这是一条更新语句
- 词法分析:将UPDATE语句拆解为token(识别关键字、表名、列名等)
- 语法分析:验证SQL语法结构是否正确(如SET子句格式、WHERE条件等)
- 语义检查:
- 验证目标表和列是否存在
- 检查用户是否有UPDATE权限
- 验证WHERE条件中引用的列是否存在
- 预处理:展开视图、处理表达式等
- 优化器生成对应的执行计划,优化器决定使用 id 索引
- 索引选择:基于WHERE条件选择最优索引(如例中的id索引)
- 访问路径决策:决定使用索引扫描还是全表扫描
- 成本评估:估算不同执行计划的I/O和CPU成本
- 连接顺序:如果是多表UPDATE,确定表连接顺序
- 生成计划:最终确定执行方案,包括使用的索引和访问方法
- 执行器负责更新,找到这一行,然后进行更新:
- 行定位:通过选定索引定位到要修改的行(如果 id=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回【MySQL 8.0后已取消查询缓存】)
- 如果使用索引:
- 通过执行器会逐行扫描,检查是否符合 WHERE 条件,效率较低索引快速定位到目标行(减少 I/O 开销)
- 如果索引是二级索引(非主键),还需要回表查询聚簇索引(主键索引)获取完整数据
- 如果没有索引(全表扫描):
- 执行器会逐行扫描,检查是否符合 WHERE 条件,效率较低
- 如果使用索引:
- 获取行锁(并发控制):MySQL 采用锁机制和 MVCC 来保证事务的隔离性
- InnoDB 行锁:
- 对符合条件的行加 排他锁(X锁) ,防止其他事务同时修改
- 如果
WHERE
条件无法走索引,可能会 锁表 (如UPDATE table WHERE name LIKE '%abc%'
)
- MVCC(读已提交/可重复读隔离级别):
- 读取数据时,基于 ReadView 判断可见性(避免脏读、不可重复读)。
- 更新时,会检查该行是否被其他事务修改(冲突检测)
- InnoDB 行锁:
- 写入 undo log(回滚日志):在修改数据前,InnoDB 会先记录 undo log (撤销日志)
- 用于事务回滚(
ROLLBACK
) - 支持 MVCC,让其他事务能读取修改前的数据(一致性读)
- 用于事务回滚(
- 修改内存中的数据页:InnoDB 不会直接修改磁盘数据,而是先更新 Buffer Pool(缓冲池) 中的缓存页
- 从磁盘加载数据页到 Buffer Pool(如果不在内存)
- 在内存中修改数据(此时磁盘数据仍是旧的)
- 标记该页为 脏页(Dirty Page) ,后续由后台线程刷盘
- 写入 redo log:为了保证数据持久性(Durability),InnoDB 会记录 redo log
- 用于崩溃恢复(即使 MySQL 宕机,也能恢复已提交的事务)
- 采用 WAL(Write-Ahead Logging) 机制,先写日志再写数据
- 提交事务:事务提交(显式或隐式),InnoDB 会进行以下操作
- 写入 binlog(二进制日志):
- 用于主从复制(Replication)和点时间恢复(PITR)
- 采用 两阶段提交(2PC) 保证 redo log 和 binlog 的一致性
- 释放锁:释放行锁,其他事务可以访问该行
- 刷盘(可选):
- 根据
sync_binlog
和innodb_flush_log_at_trx_commit
决定是否立即刷盘
- 根据
- 写入 binlog(二进制日志):
- 行定位:通过选定索引定位到要修改的行(如果 id=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回【MySQL 8.0后已取消查询缓存】)