避免在 MySQL 中使用 OR 对简单连接进行全表扫描
Posted
技术标签:
【中文标题】避免在 MySQL 中使用 OR 对简单连接进行全表扫描【英文标题】:Avoid full table scan on a simple join using OR in MySQL 【发布时间】:2021-12-08 02:25:31 【问题描述】:我有这个架构
create table table1
(
id int auto_increment primary key,
name varchar(2) null,
position int null,
);
create index table1_position
on table1 (position);
create table table_2
(
id int auto_increment primary key,
table1_id int null,
position int null,
constraint table_2_ibfk_1
foreign key (table1_id) references table1 (id)
);
create index ix_table_2_position
on table_2 (position);
create index table1_id
on table_2 (table1_id);
所以我在每个表的position
列上添加了两个索引。
现在我需要在 BOTH 表中查找一系列位置(然后加入并应用 OR 查询)
所以我有这个问题
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE table_1.position BETWEEN 5000 AND 5500
OR table_2.position BETWEEN 5000 AND 5500
但解释查询输出给了我 ALL(全表扫描)
id 1
select_type SIMPLE
table table_1
partitions
type ALL
possible_keys PRIMARY,table1_position
key
key_len
ref
rows 9929
filtered 100.0
Extra
如果我更改为 AND
如果给我预期的范围索引扫描
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE table_1.position BETWEEN 5000 AND 5500
AND table_2.position BETWEEN 5000 AND 5500
id 1
select_type SIMPLE
table table_1
partitions
type range
possible_keys PRIMARY,pos_idx2
key pos_idx2
key_len 5
ref
rows 1
filtered 100.0
Extra Using index condition
但是我在这里需要OR
语句...我怎么能让mysql 使用范围扫描索引来执行OR 语句?我可以在这里改进我的索引吗(我考虑过在 position
和 table1_id
上使用多值索引 - 外键,但它没有帮助,它执行了全表扫描)。
【问题讨论】:
你可以添加一些插入,以便我可以在我的 sql fiddle 中测试它吗? table_2.variant_id 未反映在表架构中 @ProGu 我修好了对不起,阴影错误 粗略地说,t where x or y is t where x union t where y。这篇文章是一个常见问题解答。请在考虑发布之前阅读手册和谷歌任何错误消息和许多清晰、简洁和准确的问题/问题/目标的措辞,带有和不带有您的特定名称/字符串/数字、“site:***.com”和标签;阅读许多答案。反映你的研究。 How to AskHelp centerPS minimal reproducible example 【参考方案1】:OR
的性能问题通常可以通过UNION
解决:
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE table_1.position BETWEEN 5000 AND 5500
UNION
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE table_2.position BETWEEN 5000 AND 5500;
【讨论】:
UNION [DISTINCT] 会导致额外的组合行集排序,以删除降低性能的重复项。 @Akina:但没有它,您会创建不希望的重复项。DISTINCT
操作是使用UNION
而不是OR
时要付出的代价。
查看我的答案,第一个解决方案。附加条件允许避免过度排序。
@Akina:啊,是的,这似乎是一个很好的解决方案。【参考方案2】:
可能出于性能原因,您想避免表扫描。
所以尝试将您的 OR 换成 UNION 操作。
首先,使用子查询获取您需要的一组table1.id
值,如下所示。
SELECT id FROM table_1 WHERE position BETWEEN 5000 AND 5500
UNION
SELECT table1_id FROM table_2 WHERE position BETWEEN 5000 AND 5500
UNION 的第二部分通过选择 FK 列从 table_2 中检索您需要的 table_1.id 值。
接下来,使用该子查询从 table1
获取您的行。
SELECT * FROM table_1
WHERE id IN (
SELECT id FROM table_1 WHERE position BETWEEN 5000 AND 5500
UNION
SELECT table1_id FROM table_2 WHERE position BETWEEN 5000 AND 5500
)
为加快速度,请在 table_2 上添加此复合索引。
CREATE INDEX ix_table_2_position_table1id
ON table_2 (position, table1_id);
请注意,table2 上的两个单列索引都不适用于此查询。
【讨论】:
我不明白你想要什么。您的示例查询不要求结果集中的任何 table2 列。CREATE INDEX ix_table_2_position_table1id ON table_2 (position, table1_id);
性能下降...你知道为什么吗?
我不认为这个答案中的查询等同于 OP 想要优化的查询。还是我看错了?【参考方案3】:
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE table_1.position BETWEEN 5000 AND 5500
UNION ALL
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE table_1.position NOT BETWEEN 5000 AND 5500
AND table_2.position BETWEEN 5000 AND 5500
也测试
SELECT *
FROM table_1
INNER JOIN table_2 ON table_1.id = table_2.table1_id
WHERE NOT ( table_1.position NOT BETWEEN 5000 AND 5500
AND table_2.position NOT BETWEEN 5000 AND 5500 )
【讨论】:
我都测试了,第一个没问题(但我通过首先选择 id 进入@O.Jones 解决方案)。第二个带有 WHERE NOT 子句的我得到了与 OR 相同的性能,所以并不是我所期望的。但是再次感谢您 @jossefaz 我去@O.Jones 解决方案 那是你的选择。但是,我不明白为什么您对输出行集结构与所需结构不同的解决方案感到满意。 首先选择 ids ??我不懂你什么意思。id
表格的列?我怀疑……但是什么? 第二个..我得到了相同的表现这很可能是预期的结果。
我需要你的解决方案中的索引吗?如果是,在哪一列?在位置列或复合索引中?
@jossefaz 这取决于表的统计信息。创建所有可能的复合索引,其中包括所有可能排序的 ON 和 WHERE 中提到的所有列,然后查看服务器决定使用的索引的执行计划。放下它,确保查询执行时间增加。如果是这样,那么这是此查询和数据统计的最佳索引。请记住 - 巨大的数据变化可能会使该索引不太理想。以上是关于避免在 MySQL 中使用 OR 对简单连接进行全表扫描的主要内容,如果未能解决你的问题,请参考以下文章