避免在 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 语句?我可以在这里改进我​​的索引吗(我考虑过在 positiontable1_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 对简单连接进行全表扫描的主要内容,如果未能解决你的问题,请参考以下文章

MySQL---sql语句优化

MySQL---sql语句优化

mysql sql优化和sql执行计划

MySQL之SQL语句的优化

mysql设计与优化

mysql优化