当我对以下查询执行 EXPLAIN 来获取预订的用户时
SELECT user.id
FROM user
LEFT JOIN booking
ON booking.user_id = user.id
AND booking.end_timestamp > 1706878800
AND booking.end_timestamp <= 1706882400
AND booking.status IN ('pending', 'progress', 'done');
我得到以下结果:
id | 选择类型 | 桌子 | 分区 | 类型 | 可能的键 | 键 | key_len | 参考 | 行 | 过滤 | 额外 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 简单 | 用户 | 空 | 索引 | 空 | 状态 | 1 | 空 | 17644 | 100 | 使用索引 |
1 | 简单 | 预订 | 空 | 参考 | 状态,user_id_start_timestamp,user_id_status_end_timestamp,end_timestamp | 用户 ID 状态结束时间戳 | 5 | db.user.id | 403 | 0.01 | 使用地点;使用索引 |
如您所见,它会自动使用
user_id_status_end_timestamp
表的索引 booking
。
但是当我向查询添加另一个联接以包含
address
时,这取决于 booking
表:
SELECT user.id
FROM user
LEFT JOIN booking
ON booking.user_id = user.id
AND booking.end_timestamp > 1706878800
AND booking.end_timestamp <= 1706882400
AND booking.status IN ('pending', 'progress', 'done')
LEFT JOIN address ON booking.address_id = address.id;
现在它不再对
booking
表使用任何索引,使得查询非常慢,即使它显示了相同的可能键列表:
id | 选择类型 | 桌子 | 分区 | 类型 | 可能的键 | 键 | key_len | 参考 | 行 | 过滤 | 额外 |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 简单 | 用户 | 空 | 索引 | 空 | 状态 | 1 | 空 | 17644 | 100 | 使用索引 |
1 | 简单 | 预订 | 空 | 全部 | 状态,user_id_start_timestamp,user_id_status_end_timestamp,end_timestamp | 空 | 空 | 空 | 9509525 | 100 | 检查每条记录的范围(索引图:0x98040) |
1 | 简单 | 地址 | 空 | eq_ref | 小学 | 小学 | 4 | db.booking.address_id | 1 | 100 | 使用索引 |
当然,如果我添加
USE INDEX (user_id_status_end_timestamp)
,它可以工作,但为什么MySQL在这种情况下不自动使用索引?
我使用的是 MySQL 版本 5.7.42。
删除
LEFTs
,它们对于您的应用来说似乎“错误”。
bookings
需要
INDEX(end_timestamp, status),
INDEX(status, end_timestamp)
由于您要过滤日期和状态,因此这应该是第一个要使用的表,但
LEFT
阻止了这种情况。相反,您将获得所有用户,以及其他表中的 NULL。
优化器将根据表统计信息和提供的值在这些索引之间进行选择。