查询优化基础
1. 查询执行流程详解
1.1 查询解析(Parser)
词法分析与语法分析
-
词法分析:将SQL语句分解成基本单元(Token)
- 例如:
SELECT id, name FROM users WHERE age > 18
- 被分解为:
SELECT
、id
、,
、name
、FROM
、users
、WHERE
、age
、>
、18
- 作用:确保SQL语句的基本语法正确性
- 例如:
-
语法分析:
- 检查SQL语句是否符合语法规则
- 验证表名、字段名是否存在
- 检查用户是否有相应的操作权限
- 例如:检查
users
表是否存在,id
和name
字段是否存在
语法树生成
- 抽象语法树(AST):
- 将SQL转换为树形结构,便于后续优化和执行
- 例如上述SQL会转换为:
- 根节点:SELECT
- 子节点:列名列表(id, name)、表名(users)、条件(age > 18)
1.2 查询优化(Optimizer)
逻辑优化
- 等价变换规则:
- WHERE条件化简:
WHERE age > 18 AND age > 20
→WHERE age > 20
- 子查询转化为连接:可能将子查询转为JOIN以提升性能
- 去除无用条件:
WHERE 1=1
这样的条件会被删除
- WHERE条件化简:
物理优化
- 访问路径评估:
- 决定是否使用索引
- 例如:
WHERE age > 18
,如果age字段有索引,评估是全表扫描快还是走索引快
- 成本估算:
- I/O成本:读取数据块的成本
- CPU成本:处理数据的计算成本
- 例如:全表扫描可能需要读取100个数据块,而索引扫描只需要读取10个数据块
1.3 执行计划生成
访问方法选择
- 表扫描方式:
- 全表扫描:适用于需要访问表中大部分数据时
- 索引扫描:适用于通过索引可以快速定位少量数据时
- 例如:
SELECT * FROM users WHERE age = 20
- 如果age有索引,且符合条件的数据较少,选择索引扫描
- 如果符合条件的数据量大,选择全表扫描
连接算法选择
-
Nested Loop Join:
- 适用于小表连接或有索引的连接条件
- 例如:
users
表和orders
表连接,如果orders
表的user_id
有索引
-
Hash Join:
- 适用于大表连接且无合适索引时
- 例如:两个大表在无索引字段上连接
-
Sort Merge Join:
- 适用于已经排序的数据集连接
- 例如:按时间范围连接的场景
1.4 查询执行(Executor)
算子执行
-
扫描算子:
- 负责从存储层读取数据
- 例如:从硬盘读取表数据或索引数据
-
连接算子:
- 负责将多个表的数据按照指定条件连接
- 例如:将用户表和订单表按用户ID连接
-
聚合算子:
- 负责进行分组和聚合计算
- 例如:
GROUP BY
和COUNT
、SUM
等操作
结果处理
-
结果排序:
- 按照ORDER BY子句进行排序
- 可能使用内存排序或磁盘排序
-
分页处理:
- 处理LIMIT和OFFSET
- 返回指定范围的结果集
优化建议
- 合理使用索引:根据查询条件创建适当的索引
- 避免使用SELECT *:只查询需要的字段
- 适当使用LIMIT:限制结果集大小
- 定期更新统计信息:确保优化器能做出正确的选择
- 合理设置JOIN顺序:小表驱动大表