一、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优化是一个系统工程,需要结合业务特点、数据量、硬件资源等多方面因素综合考虑。最好的优化往往发生在设计阶段,合理的数据库设计和索引策略比后期的调优更重要。