MySQL处理SELECT语句第一步是连接器验证用户权限,之后若查询缓存启用且命中则直接返回结果;但MySQL 8.0已移除查询缓存,5.7默认关闭,故实际必经下一步是解析器将SQL解析为语法树并检查语法错误。

SQL 进入 MySQL 后第一步干了啥
客户端发来的 SELECT * FROM users WHERE id = 123,MySQL 不是直接去磁盘查数据。它先走连接器验证权限,再交由查询缓存(如果开着且命中)——但注意:query_cache_type=ON 在 MySQL 8.0 已被彻底移除,5.7 默认也关了,别白费劲配它。
真正必经的下一步是解析器:把 SQL 字符串拆成语法树,检查关键字、表名、列名是否存在拼写错误。这时候报的错像 ERROR 1064 (42000),基本就是语法或保留字冲突,比如用 order 当表名却没加反引号。
之后预处理器会校验语义,例如确认 users 表真有 id 列,GROUP BY 里有没有漏掉非聚合字段。这步出错是 ERROR 1054 (42S22) 或 ERROR 1140 (42000)。
优化器决定怎么查,而不是你怎么写的
你写 SELECT * FROM a JOIN b ON a.id = b.a_id WHERE b.status = 'active',优化器可能把 WHERE 条件下推到 b 表扫描前,也可能先扫 a 再关联——取决于统计信息是否准确、索引是否存在、以及 JOIN 顺序代价估算。
常见误判场景:
- 表数据量小但没 ANALYZE TABLE,优化器以为某列区分度低,放弃走索引
- WHERE 中对字段用了函数,如
WHERE DATE(create_time) = '2024-01-01',导致索引失效(应改用范围查询) - 多列索引中,查询只用了后缀列(如索引是
(a,b,c),却只查WHERE c = 1),无法命中
用 EXPLAIN 看 type 是 ALL 还是 ref,看 key 是否为 NULL,比猜更可靠。
执行器调用存储引擎接口,不是“直接读文件”
执行器不关心数据存在哪,它只按统一接口向引擎要数据:调 ha_innobase::index_read() 或 ha_myisam::rnd_next()。InnoDB 的行锁、MVCC、聚簇索引组织方式,全在引擎层实现;执行器只负责循环调用、过滤 WHERE 条件、组装结果集。
这意味着:
-
SELECT ... FOR UPDATE能否加锁成功,取决于 InnoDB 是否能定位到记录(比如 WHERE 条件没走索引,就会锁整张表) - 事务隔离级别影响的是 InnoDB 的可见性判断逻辑,执行器只接收“该不该返回这一行”的布尔结果
- 如果表用的是 MyISAM,执行器拿到的永远是最新物理数据,没有 MVCC,也没有行级锁
所以别在执行器层找“为什么没锁住某行”,得看 SHOW ENGINE INNODB STATUS 里的锁信息。
日志写入和刷盘时机容易被当成“已提交”
一条 UPDATE 执行完,客户端收到 “Query OK”,不代表数据已落盘。InnoDB 先写 redo log(物理日志,保证崩溃可恢复),再更新 buffer pool;binlog(逻辑日志,用于主从)由 server 层写,但刷盘受 sync_binlog 控制。
关键陷阱:
-
innodb_flush_log_at_trx_commit=1时,redo log 每次 commit 都 fsync,安全但慢;设为 0 或 2,断电可能丢最近 1 秒事务 -
sync_binlog=1和innodb_flush_log_at_trx_commit=1必须同时开,才能保证主从一致性和崩溃安全性(即“双一”配置) - 大事务写 binlog 是在提交前一次性写入,若中途 kill,binlog 可能不完整,导致备库复制中断
线上环境别碰 innodb_flush_log_at_trx_commit=0,除非你明确接受丢失最近事务的风险。


















