避免在 MySQL 中使用 OR 对简单联接进行完整 table 扫描
Avoid full table scan on a simple join using OR in MySQL
我有这个架构
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);
所以我在每个 table 的列 position
上添加了两个索引。
现在我需要在 BOTH table 中寻找一个位置范围(通过加入 then 并应用 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
但是 Explain 查询输出给我全部(完整 table 扫描)
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
上的多值索引 - 外键,但它没有帮助并且它执行了完整的 table 扫描).
您可能出于性能原因想要避免 table 扫描。
所以尝试将 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 上的两个单列索引都对此查询没有用。
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 )
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;
我有这个架构
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);
所以我在每个 table 的列 position
上添加了两个索引。
现在我需要在 BOTH table 中寻找一个位置范围(通过加入 then 并应用 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
但是 Explain 查询输出给我全部(完整 table 扫描)
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
上的多值索引 - 外键,但它没有帮助并且它执行了完整的 table 扫描).
您可能出于性能原因想要避免 table 扫描。
所以尝试将 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 上的两个单列索引都对此查询没有用。
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 )
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;