小红书Java面试被问:SQL语句的执行过程解析
MySQL SQL语句执行流程详解:SQL语句执行经历连接器、分析器、优化器、执行器和存储引擎五个核心阶段。连接器负责建立连接和权限验证;分析器进行词法、语法和语义分析;优化器生成最优执行计划;执行器调用存储引擎接口执行;存储引擎完成数据读写。优化重点包括执行计划分析、合理索引设计(遵循最左前缀原则)、避免全表扫描等。MySQL 8.0移除了查询缓存,推荐使用EXPLAIN分析查询、监控慢SQL,
一、SQL语句完整执行流程图
text
复制
下载
┌─────────────────────────────────────────────────────┐
│ 客户端发起SQL请求 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 连接器:建立连接、权限验证 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 查询缓存:命中则直接返回(MySQL 8.0移除) │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 分析器:词法分析、语法分析、语义分析 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 优化器:生成最优执行计划 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 执行器:调用存储引擎执行计划 │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 存储引擎:磁盘读写、索引查找等 │
└─────────────────────────────────────────────────────┘
二、各阶段详细解析
1. 连接器阶段(Connector)
🔌 连接建立过程
sql
复制
下载
-- 客户端连接命令 mysql -h$ip -P$port -u$user -p -- 连接建立后的内部处理 1. TCP三次握手建立连接 2. 验证用户名和密码 3. 权限验证(读取user表权限信息) 4. 分配连接线程(线程池管理)
📊 连接状态管理
sql
复制
下载
-- 查看当前连接 SHOW PROCESSLIST; -- 结果示例: -- Id User Host db Command Time State Info -- 5 root localhost:1234 test Query 0 starting SELECT * FROM users -- 连接参数配置 -- wait_timeout:非交互连接超时时间(默认8小时) -- interactive_timeout:交互连接超时时间(默认8小时) -- max_connections:最大连接数(默认151) -- 连接资源消耗 -- 每个连接占用约256KB内存 -- 连接过多会导致内存耗尽和上下文切换开销
2. 查询缓存阶段(Query Cache) - MySQL 8.0已移除
🗂️ 缓存机制原理
sql
复制
下载
-- 缓存键的生成 -- Key = 查询语句本身 + 当前数据库 + 客户端协议版本等 -- Value = 查询结果集 -- 缓存命中条件 SELECT * FROM users WHERE id = 1; -- 命中 SELECT * FROM users WHERE id = 1; -- 完全相同的语句 SELECT * FROM users WHERE id = 2; -- 不命中(参数不同) SELECT * FROM USERS WHERE ID = 1; -- 不命中(大小写不同) -- 导致缓存失效的操作 INSERT INTO users ... -- 相关表的所有缓存失效 UPDATE users ... -- 相关表的所有缓存失效 DELETE FROM users ... -- 相关表的所有缓存失效 ALTER TABLE users ... -- 表结构变更,缓存失效
⚡ 缓存性能问题
sql
复制
下载
-- 为什么MySQL 8.0移除了查询缓存? -- 1. 缓存失效过于频繁 -- 2. 并发场景下缓存锁竞争严重 -- 3. 内存占用与实际收益不成正比 -- 4. 更推荐使用应用层缓存(如Redis)
3. 分析器阶段(Parser)
🔍 词法分析(Lexical Analysis)
sql
复制
下载
-- 原始SQL语句 SELECT id, name FROM users WHERE age > 18; -- 词法分析结果(Token流): [关键字:SELECT] [标识符:id] [逗号:] [标识符:name] [关键字:FROM] [标识符:users] [关键字:WHERE] [标识符:age] [操作符:>] [数字:18] [分号:;]
📐 语法分析(Syntax Analysis)
sql
复制
下载
-- 构建语法树(AST - Abstract Syntax Tree)
SELECT
├── COLUMNS
│ ├── id
│ └── name
├── FROM
│ └── users
└── WHERE
└── age > 18
-- 语法检查规则
-- 1. 关键字顺序是否正确
-- 2. 表名、列名是否存在
-- 3. WHERE条件格式是否正确
-- 4. GROUP BY/HAVING/ORDER BY顺序
-- 常见语法错误
SELECT * FORM users; -- 错误:FORM应为FROM
SELECT * FROM user WHERE; -- 错误:WHERE后缺少条件
🎯 语义分析(Semantic Analysis)
sql
复制
下载
-- 语义检查内容 -- 1. 数据类型兼容性检查 SELECT name + age FROM users; -- 字符串+数字,可能报错 -- 2. 权限验证(表级、列级) SELECT salary FROM employees; -- 检查是否有salary列的查询权限 -- 3. 完整性约束检查 INSERT INTO orders(user_id) VALUES(999); -- 检查外键约束 -- 4. 视图展开 -- 如果查询视图,将视图定义展开为基表查询
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
4. 优化器阶段(Optimizer)
⚙️ 优化器工作流程
sql
复制
下载
-- 基于成本的优化器(CBO - Cost-Based Optimizer) 1. 收集统计信息 - 表的数据量(rows) - 列的基数(cardinality) - 索引的选择性 - 数据分布直方图 2. 生成候选执行计划 -- 示例查询 SELECT * FROM orders o JOIN customers c ON o.customer_id = c.id WHERE o.status = 'SHIPPED' AND c.country = 'US'; -- 可能的执行计划: -- Plan A: 先过滤orders,再join customers -- Plan B: 先过滤customers,再join orders -- Plan C: 使用不同的join顺序和算法 3. 估算每个计划的成本 -- 成本因素: -- IO成本:磁盘读取次数 -- CPU成本:计算复杂度 -- 内存成本:临时表、排序等 -- 网络成本(分布式数据库) 4. 选择成本最低的执行计划
🔄 优化器优化策略
1. 查询重写优化
sql
复制
下载
-- 原始查询 SELECT * FROM users WHERE id IN (1, 2, 3); -- 优化后等价查询 SELECT * FROM users WHERE id = 1 UNION ALL SELECT * FROM users WHERE id = 2 UNION ALL SELECT * FROM users WHERE id = 3; -- 其他重写优化: -- 视图合并(View Merging) -- 谓词下推(Predicate Pushdown) -- 子查询优化(Subquery Optimization) -- 消除冗余(Redundancy Elimination)
2. 访问路径优化
sql
复制
下载
-- 不同访问路径的成本估算 EXPLAIN SELECT * FROM users WHERE age > 20; -- 可能的访问路径: -- 1. 全表扫描(Table Scan) -- 成本:表数据页数 × 读取单页成本 -- 2. 索引扫描(Index Scan) -- 成本:索引高度 + 索引叶节点数 + 回表成本 -- 3. 索引覆盖扫描(Covering Index) -- 成本:索引高度 + 索引叶节点数(无需回表) -- 4. 索引范围扫描(Index Range Scan) -- 5. 索引快速全扫描(Index Fast Full Scan) -- 6. 索引跳跃扫描(Index Skip Scan)
3. 连接优化
sql
复制
下载
-- 连接算法选择 -- 示例查询 SELECT * FROM orders o JOIN order_items i ON o.id = i.order_id; -- 可选的连接算法: -- 1. Nested Loop Join(嵌套循环连接) -- 适用:小表驱动大表,有高效索引 -- 2. Hash Join(哈希连接) -- 适用:无索引,等值连接,内存充足 -- 步骤: -- a. 构建阶段:扫描小表,构建哈希表 -- b. 探测阶段:扫描大表,在哈希表中查找匹配 -- 3. Sort Merge Join(排序合并连接) -- 适用:非等值连接,数据已排序或可排序 -- 连接顺序优化 -- 多表连接时,优化器会尝试不同的连接顺序 -- 对于n个表,可能的连接顺序数 = (2n-2)! / (n-1)! -- 优化器使用动态规划算法选择最优顺序
4. 排序和分组优化
sql
复制
下载
-- 排序优化 SELECT * FROM users ORDER BY name; -- 排序算法: -- 1. 内存排序(Quick Sort/Heap Sort) -- 适用:数据量小,能放入排序缓冲区(sort_buffer_size) -- 2. 外部归并排序(External Merge Sort) -- 适用:数据量大,需使用磁盘临时文件 -- 3. 使用索引避免排序 -- 如果ORDER BY列有索引,可以直接按索引顺序读取 -- 分组优化 SELECT department, COUNT(*) FROM employees GROUP BY department; -- 分组算法: -- 1. 松散索引扫描(Loose Index Scan) -- 适用:GROUP BY列是索引前缀 -- 2. 紧凑索引扫描(Tight Index Scan) -- 适用:GROUP BY列不是索引前缀但可通过索引访问 -- 3. 临时表分组(Temporary Table Grouping) -- 适用:无法使用索引,数据量较大
📈 优化器统计信息
sql
复制
下载
-- MySQL统计信息收集 ANALYZE TABLE users; -- 收集表的统计信息 -- 查看统计信息 SHOW TABLE STATUS LIKE 'users'; -- Rows: 表的估计行数 -- Data_length: 数据大小 -- Index_length: 索引大小 -- 查看索引统计信息 SHOW INDEX FROM users; -- Cardinality: 索引的唯一值数量 -- 选择性 = Cardinality / 总行数 -- 直方图统计(MySQL 8.0+) ANALYZE TABLE users UPDATE HISTOGRAM ON age; -- 用于优化非等值查询 SELECT * FROM users WHERE age > 30;
5. 执行器阶段(Executor)
🚀 执行器工作流程
sql
复制
下载
-- 执行器调用接口 -- 以InnoDB引擎为例: 1. 打开表获取表定义信息 2. 调用存储引擎接口读取数据 3. 应用WHERE条件过滤 4. 执行连接、排序、分组等操作 5. 返回结果给客户端 -- 执行器与存储引擎的交互 -- 执行器遵循"火山模型"(Volcano Model) -- 每次调用next()方法获取下一行数据
🔧 执行计划解读
sql
复制
下载
-- 使用EXPLAIN查看执行计划
EXPLAIN FORMAT=JSON
SELECT * FROM orders o
JOIN customers c ON o.customer_id = c.id
WHERE o.amount > 1000 AND c.country = 'US';
-- 关键信息解读:
{
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1024.50" -- 查询总成本
},
"nested_loop": [ -- 连接算法
{
"table": {
"table_name": "o", -- 驱动表
"access_type": "range", -- 访问类型
"possible_keys": ["idx_amount"],
"key": "idx_amount", -- 使用的索引
"rows_examined_per_scan": 5000,
"rows_produced_per_join": 1000,
"filtered": "20.00", -- 过滤比例
"cost_info": {
"read_cost": "500.00",
"eval_cost": "200.00",
"prefix_cost": "700.00" -- 累积成本
}
}
},
{
"table": {
"table_name": "c", -- 被驱动表
"access_type": "eq_ref",
"possible_keys": ["PRIMARY"],
"key": "PRIMARY",
"rows_examined_per_scan": 1,
"filtered": "100.00",
"cost_info": {
"read_cost": "324.50",
"eval_cost": "0.00",
"prefix_cost": "1024.50" -- 最终成本
}
}
}
]
}
}
6. 存储引擎阶段(Storage Engine)
💾 数据读取过程
sql
复制
下载
-- InnoDB数据读取示例 SELECT * FROM users WHERE id = 1; -- 读取步骤: 1. 根据id=1计算B+树查找路径 2. 从根节点开始,逐层查找 3. 在叶节点找到对应记录 4. 如果使用聚簇索引,直接获取行数据 5. 如果使用二级索引,需要回表查询 -- 数据页结构 -- 每个数据页默认16KB,包含: -- 页头(Page Header):页的元信息 -- 行记录(Row Records):实际数据 -- 页目录(Page Directory):行记录的槽位 -- 页尾(Page Tailer):校验和
⚡ 缓冲池优化
sql
复制
下载
-- InnoDB缓冲池(Buffer Pool) -- 内存缓存池,减少磁盘IO -- 查看缓冲池状态 SHOW ENGINE INNODB STATUS\G -- Buffer pool size: 缓冲池总大小 -- Free buffers: 空闲页数量 -- Database pages: 数据页数量 -- Modified db pages: 脏页数量 -- 缓冲池算法:LRU(最近最少使用) -- 新读取的页放入LRU列表的midpoint位置 -- 经常访问的页会移到LRU列表的热端 -- 关键配置参数: -- innodb_buffer_pool_size: 缓冲池大小(推荐物理内存的70-80%) -- innodb_buffer_pool_instances: 缓冲池实例数(减少锁竞争)
三、不同SQL类型的执行差异
1. SELECT查询
sql
复制
下载
-- 简单查询 SELECT * FROM users WHERE id = 1; -- 执行路径:索引查找 → 返回数据 -- 复杂查询 SELECT u.name, COUNT(o.id) FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.age > 18 GROUP BY u.id HAVING COUNT(o.id) > 5 ORDER BY COUNT(o.id) DESC; -- 执行路径:过滤 → 连接 → 分组 → 过滤 → 排序 → 返回
2. INSERT操作
sql
复制
下载
-- 单条插入
INSERT INTO users(name, age) VALUES('John', 25);
-- 执行路径:
-- 1. 检查约束(主键、唯一、外键)
-- 2. 申请自增ID(如果有自增列)
-- 3. 写入undo log(用于回滚)
-- 4. 写入redo log(持久化保证)
-- 5. 修改缓冲池数据页
-- 6. 写入binlog(主从复制)
-- 批量插入
INSERT INTO users(name, age) VALUES('A',20),('B',21),('C',22);
-- 优化:减少事务提交次数,使用批量写入
3. UPDATE操作
sql
复制
下载
UPDATE users SET age = age + 1 WHERE id = 1; -- 执行路径: -- 1. 查找要更新的行(使用索引) -- 2. 写入undo log(记录旧值) -- 3. 写入redo log -- 4. 修改缓冲池数据页 -- 5. 写入binlog -- 6. 如果是InnoDB,使用行锁锁定该行
4. DELETE操作
sql
复制
下载
DELETE FROM users WHERE id = 1; -- 执行路径: -- 1. 查找要删除的行 -- 2. 写入undo log -- 3. 写入redo log -- 4. 标记行为删除状态(不会立即物理删除) -- 5. Purge线程异步清理删除标记的行
四、性能调优关注点
1. 执行计划分析
sql
复制
下载
-- 使用EXPLAIN分析 EXPLAIN SELECT * FROM users WHERE age > 20; -- 关键字段解读: -- type: 访问类型(const, eq_ref, ref, range, index, ALL) -- key: 实际使用的索引 -- rows: 估计需要检查的行数 -- filtered: 条件过滤的百分比 -- Extra: 额外信息(Using index, Using temporary, Using filesort) -- 使用EXPLAIN ANALYZE(MySQL 8.0.18+) EXPLAIN ANALYZE SELECT * FROM users WHERE age > 20; -- 显示实际执行时间、循环次数等详细信息
2. 索引优化
sql
复制
下载
-- 创建合适的索引 -- 示例:复合索引的最左前缀原则 CREATE INDEX idx_name_age ON users(name, age); -- 以下查询能使用索引: SELECT * FROM users WHERE name = 'John'; SELECT * FROM users WHERE name = 'John' AND age = 25; -- 以下查询不能使用索引: SELECT * FROM users WHERE age = 25; -- 不符合最左前缀 -- 索引选择策略: -- 1. 选择性高的列在前 -- 2. 等值查询列在前,范围查询列在后 -- 3. 避免冗余索引
3. 查询优化
sql
复制
下载
-- 避免全表扫描 -- 坏:无索引条件 SELECT * FROM users WHERE phone LIKE '%1234%'; -- 好:使用索引前缀 SELECT * FROM users WHERE phone LIKE '1234%'; -- 避免使用SELECT * -- 坏:读取所有列 SELECT * FROM users WHERE id = 1; -- 好:只读取需要的列 SELECT name, age FROM users WHERE id = 1; -- 使用LIMIT分页优化 -- 坏:偏移量大时性能差 SELECT * FROM users LIMIT 1000000, 20; -- 好:使用索引覆盖+游标分页 SELECT * FROM users WHERE id > 1000000 LIMIT 20;
五、高级特性与优化
1. 查询执行计划缓存
sql
复制
下载
-- Prepared Statement执行计划缓存 PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?'; SET @id = 1; EXECUTE stmt USING @id; -- 优点: -- 1. 避免重复解析和优化 -- 2. 防止SQL注入 -- 3. 执行计划重用 -- 查看执行计划缓存 SHOW STATUS LIKE 'Qcache%'; -- 查询缓存状态
2. 并行查询(MySQL 8.0+)
sql
复制
下载
-- 启用并行查询 SET SESSION innodb_parallel_read_threads = 4; -- 适合并行查询的场景: -- 1. 全表扫描 -- 2. 大表统计查询 -- 3. 无索引的聚合查询 -- 查看并行查询使用情况 EXPLAIN ANALYZE SELECT COUNT(*) FROM large_table;
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
3. 物化视图与查询重写
sql
复制
下载
-- MySQL 8.0+ 支持物化视图 CREATE TABLE sales_summary AS SELECT product_id, SUM(amount) as total FROM sales GROUP BY product_id; -- 定期刷新物化视图 REFRESH MATERIALIZED VIEW sales_summary; -- 查询重写优化 -- 优化器可能将查询重写为使用物化视图 SELECT product_id, SUM(amount) FROM sales GROUP BY product_id; -- 可能被重写为: SELECT product_id, total FROM sales_summary;
六、监控与诊断工具
1. 性能监控
sql
复制
下载
-- 查看慢查询 SHOW VARIABLES LIKE 'slow_query_log%'; SHOW VARIABLES LIKE 'long_query_time'; -- 查看当前执行查询 SHOW FULL PROCESSLIST; -- 查看锁信息 SHOW ENGINE INNODB STATUS\G -- 查看LOCK WAIT部分 -- 查看IO统计 SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
2. 性能诊断
sql
复制
下载
-- 使用Performance Schema -- 查看等待事件 SELECT * FROM performance_schema.events_waits_current; -- 查看语句统计 SELECT * FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 10; -- 使用sys schema(MySQL 5.7+) -- 查看冗余索引 SELECT * FROM sys.schema_redundant_indexes; -- 查看未使用索引 SELECT * FROM sys.schema_unused_indexes;
七、最佳实践总结
✅ 优化要点:
-
理解执行计划:使用EXPLAIN分析查询
-
合理设计索引:遵循最左前缀原则
-
避免全表扫描:为WHERE条件建立索引
-
减少数据传输:避免SELECT *,使用LIMIT
-
优化连接查询:小表驱动大表,使用索引
-
合理使用事务:避免长事务,及时提交
-
监控慢查询:定期分析优化慢SQL
📊 性能指标参考:
-
响应时间:简单查询<10ms,复杂查询<100ms
-
QPS:根据业务需求设定目标
-
连接数:避免超过max_connections的70%
-
缓冲池命中率:>95%
-
索引命中率:>95%
记住:SQL优化是一个系统工程,需要结合业务特点、数据量、硬件资源等多方面因素综合考虑。最好的优化往往发生在设计阶段,合理的数据库设计和索引策略比后期的调优更重要。
更多推荐

所有评论(0)