在 MySQL 查询执行过程中,涉及到多表关联操作时,驱动表(Driving Table)和 被驱动表(Driven Table)是重要的概念。理解这两个概念对优化查询性能至关重要。驱动表是查询执行过程中首先扫描的表,被驱动表是随后与驱动表进行关联的表。选择合适的驱动表可以显著提高查询性能,反之则可能导致查询性能下降。
为了详细解释驱动表和被驱动表的工作原理,我们将从以下几个方面进行分析:
1. 驱动表和被驱动表的定义
-
驱动表(Driving Table):在多表连接查询中,MySQL 会首先扫描的表称为驱动表。驱动表的每一行数据都需要与被驱动表中的数据进行匹配,以生成最终的结果集。
-
被驱动表(Driven Table):驱动表扫描完成后,MySQL 会将驱动表中的每一行与被驱动表中的数据进行关联。被驱动表通常是根据驱动表的结果进一步进行过滤的表。
举个简单的例子:
SELECT * FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE e.salary > 5000;
在这个查询中,employees
表和 departments
表通过 department_id
进行关联。MySQL 执行查询时会选择其中一个表作为驱动表,另一个表作为被驱动表。通常情况下,驱动表会是小表或者经过过滤条件限制行数的表。
2. 驱动表和被驱动表的选择策略
MySQL 优化器在处理多表查询时,会根据以下几个因素来选择驱动表和被驱动表:
2.1 表的大小
通常,MySQL 会优先选择较小的表作为驱动表。这样可以减少扫描表时的 I/O 操作和数据量,并且在驱动表中的每一行去被驱动表中寻找匹配数据时,可以避免大量无效的数据匹配。
2.2 表的过滤条件
过滤条件(WHERE
子句或 JOIN
条件)越严格,筛选出的数据行数越少。通常,MySQL 会选择那些在查询初期就可以大幅度减少结果集大小的表作为驱动表。因为如果驱动表筛选出的行数较少,后续与被驱动表的关联操作的开销也会降低。
2.3 索引的使用
如果某张表上有良好的索引,MySQL 可能会优先选择该表作为驱动表。通过索引扫描,MySQL 能更高效地访问记录,减少全表扫描的开销。
2.4 关联条件
MySQL 优化器还会根据表与表之间的关联条件选择驱动表。关联条件越高效(如使用主键或唯一索引),被驱动表的查询效率越高,因此 MySQL 可能会倾向于选择与这种高效关联的表作为被驱动表。
3. MySQL 执行计划中的驱动表和被驱动表
MySQL 使用 基于成本的优化器(Cost-Based Optimizer, CBO),它会基于表的大小、索引使用情况、过滤条件等信息,选择合适的驱动表和被驱动表。在执行查询时,优化器会生成执行计划并通过 EXPLAIN
命令来展示驱动表和被驱动表的选择情况。
3.1 通过 EXPLAIN
查看驱动表和被驱动表
可以使用 EXPLAIN
来查看 MySQL 的执行计划,帮助判断哪个表是驱动表,哪个表是被驱动表。
EXPLAIN SELECT * FROM employees e JOIN departments d ON e.department_id = d.id WHERE e.salary > 5000;
执行该 EXPLAIN
命令后,MySQL 会展示每个表的扫描顺序,以及每个表的访问方法(如全表扫描、索引扫描等)。通常,第一个出现的表是驱动表,之后的表则是被驱动表。
从上述结果中可以看到,employees
表是驱动表,因为它在执行计划中的顺序靠前,并且它通过索引过滤部分数据后再去关联 departments
表。
4. 驱动表和被驱动表的源码分析
MySQL 在执行查询时,底层的表连接逻辑主要体现在 join_optimizer 中。在源码中,这一过程可以追溯到 sql/sql_select.cc
文件中的 JOIN
类,特别是 make_join_plan
函数,该函数负责生成查询计划,其中的驱动表和被驱动表的选择逻辑尤为重要。
4.1 JOIN::optimize()
函数
JOIN::optimize()
是查询优化的主要入口,该函数负责生成查询的执行计划,包括表的顺序和连接方式。在优化过程中,它会尝试不同的连接顺序,并根据成本计算选择最优方案。
void JOIN::optimize() {
...
// 生成查询计划
make_join_plan();
...
}
4.2 make_join_plan()
函数
make_join_plan()
函数根据优化器的成本模型选择合适的表作为驱动表和被驱动表。它会评估不同的表连接顺序,并基于查询的代价模型决定驱动表。
bool JOIN::make_join_plan() {
...
// 尝试不同的连接顺序
best_read = best_position();
...
}
4.3 best_position()
函数
best_position()
函数会计算每个表作为驱动表的代价,选择代价最小的方案。它会考虑表的大小、过滤条件、索引使用情况等因素,最终决定驱动表和被驱动表的顺序。
bool JOIN::best_position() {
for (each possible table combination) {
// 计算表连接顺序的代价
double cost = calculate_cost();
if (cost < best_cost) {
best_cost = cost;
// 保存最优的连接顺序
best_position = current_position;
}
}
...
}
5. 驱动表和被驱动表的优化技巧
5.1 基于索引优化驱动表选择
合理设计索引可以影响驱动表的选择。例如,确保在过滤条件中使用的字段上有索引,可以提高该表被选择为驱动表的可能性。
CREATE INDEX idx_salary ON employees(salary);
在上述查询中,employees
表的 salary
字段上创建索引后,MySQL 优化器会优先选择该表作为驱动表,因为索引可以帮助更快地过滤数据。
5.2 控制表的大小
如果可以控制表的数据量(例如通过分区、拆表等技术),可以将较小的表设为驱动表。例如,如果 departments
表非常小,而 employees
表非常大,通常应该选择 departments
作为驱动表。
5.3 合理使用 STRAIGHT_JOIN
在某些情况下,MySQL 的优化器可能没有选择最优的驱动表。可以使用 STRAIGHT_JOIN
强制优化器按照指定的顺序执行连接,确保较小或过滤条件更严格的表作为驱动表。
SELECT * FROM departments d STRAIGHT_JOIN employees e ON d.id = e.department_id WHERE e.salary > 5000;
通过 STRAIGHT_JOIN
强制指定连接顺序,确保 departments
表作为驱动表。
6. 总结:驱动表和被驱动表的选择原则
- 驱动表 是查询中首先扫描的表,通常选择小表或有严格过滤条件的表。
- 被驱动表 是后续与驱动表进行关联的表,通常是较大表或者没有严格过滤条件的表。
- MySQL 优化器根据表的大小、过滤条件、索引使用情况等因素选择驱动表,并在执行计划中做出最优选择。
- 可以通过
EXPLAIN
命令查看执行计划,判断驱动表和被驱动表的选择情况。 - 优化驱动表选择的方式包括:合理设计索引、控制表的数据量、使用
STRAIGHT_JOIN
强制指定连接顺序等。
理解并优化驱动表和被驱动表的选择对于提升 MySQL 查询性能具有重要意义。