MySQL在执行联表查询时,主要使用以下三种算法:
-- 基本原理:对于左表的每一行,都要在右表中查找所有匹配的行
-- 示例查询
SELECT * FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id;
-- 伪代码实现
for each row in orders {
for each row in order_items {
if (orders.id = order_items.order_id) {
output matched row
}
}
}
-- 当被驱动表上有索引时使用
-- 示例:order_items表的order_id字段上有索引
SELECT * FROM orders o -- 驱动表
INNER JOIN order_items oi ON o.id = oi.order_id; -- 被驱动表
-- 伪代码实现
for each row in orders {
lookup order_items using index(order_id) -- 使用索引查找
}
-- 当被驱动表上没有索引时使用
-- 使用join buffer缓存驱动表的数据
SET join_buffer_size = 1048576; -- 设置join buffer大小为1MB
-- 伪代码实现
while (rows in orders) {
load join_buffer with orders rows
for each row in order_items {
for each row in join_buffer {
if (orders.id = order_items.order_id) {
output matched row
}
}
}
}
-- MySQL 8.0.18版本后支持
-- 适用于等值连接
SELECT * FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id;
-- 执行过程
1. Build阶段:
- 选择小表构建哈希表
- 使用连接键作为哈希键
2. Probe阶段:
- 扫描大表
- 对每一行数据计算哈希值
- 在哈希表中查找匹配项
-- 适用于连接键已经排序的情况
SELECT * FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id
ORDER BY o.id;
-- 执行过程
1. Sort阶段:
- 对两个表按连接键进行排序
2. Merge阶段:
- 同时扫描两个排序后的表
- 合并匹配的行
EXPLAIN SELECT * FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id;
优化器会考虑以下因素:
-- 当order_items表的order_id字段有索引时
CREATE INDEX idx_order_id ON order_items(order_id);
-- 优化器可能选择:
1. orders作为驱动表
2. 使用Index Nested-Loop Join
3. 利用order_items表上的索引
-- 在被驱动表的连接字段上创建索引
CREATE INDEX idx_order_id ON order_items(order_id);
CREATE INDEX idx_product_id ON order_items(product_id);
-- 复合索引的选择
CREATE INDEX idx_order_product ON order_items(order_id, product_id);
-- 优先选择小表作为驱动表
SELECT * FROM small_table s
INNER JOIN big_table b ON s.id = b.small_id;
-- 强制指定连接顺序
SELECT STRAIGHT_JOIN * FROM small_table s
INNER JOIN big_table b ON s.id = b.small_id;
-- 设置join buffer大小
SET join_buffer_size = 4194304; -- 设置为4MB
-- 监控join buffer使用情况
SHOW STATUS LIKE 'Join%';
-- 大数据量分页优化
SELECT o.*, oi.*
FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id
WHERE o.id > last_id -- 使用主键限制
LIMIT 100;
-- 错误示例
SELECT * FROM orders, order_items;
-- 正确示例
SELECT * FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id;
-- 使用IS NULL处理
SELECT * FROM orders o
LEFT JOIN order_items oi ON o.id = oi.order_id
WHERE oi.id IS NULL; -- 查找没有订单项的订单
-- 拆分复杂查询
-- 代替直接JOIN多表
WITH order_info AS (
SELECT o.id, o.order_no, oi.product_id
FROM orders o
INNER JOIN order_items oi ON o.id = oi.order_id
)
SELECT oi.*, p.name
FROM order_info oi
INNER JOIN products p ON oi.product_id = p.id;