在 MySql 查询中替换“OR EXISTS”,这样我可以获得更好的性能结果
Posted
技术标签:
【中文标题】在 MySql 查询中替换“OR EXISTS”,这样我可以获得更好的性能结果【英文标题】:Substitute "OR EXISTS" in MySql query so i can get better perfomance results 【发布时间】:2021-07-22 21:28:29 【问题描述】:这个查询在 mysql 8 中需要很长时间才能完成,做一些研究我发现这个代码中的“EXISTS”在某些查询中可能非常慢。 当我删除“OR EXISTS”子查询部分时,它会在不到一秒的时间内运行。
所以我需要在这个查询中替换“OR EXISTS”,这样我就可以获得我需要的所有用户:
SELECT u.name,
u.email,
u.cpf,
u.register,
r.name AS role_name,
s.name AS sector_name,
b.name AS branch_name,
u.status
FROM users u
INNER JOIN roles r ON r.id = u.role_id
INNER JOIN sectors s ON s.id = u.sector_id
INNER JOIN branches b ON b.id = u.branch_id
WHERE u.status = 2 OR EXISTS (
SELECT *
FROM user_recovery ur
WHERE ur.user_id = u.id
AND ur.status_recovery = 1
)
有没有办法在没有“OR EXISTS”的情况下做到这一点?
【问题讨论】:
不要忘记现代数据库做了很多查询优化。例如,您可以在EXITS
中更改为SELECT 1 ...
,但不确定是否已经完成了类似的操作。
@PM77-1 - 嗯,MySQL 还是有点“简单”。我的答案应用了两个我从未见过的优化。
OR
是慢的部分,而不是EXISTS
。
left outer join
user to user_recovery
if user_recovery
在 user_id 上是唯一的。如果不是,则从用户恢复中派生一个具有不同 user_id 的表并加入该表,然后 OR。
【参考方案1】:
或者可以强制执行全面扫描
试试
您无法摆脱 eXISTS 子句,因为它会增加返回的行数。
在用户状态和 user_recovery userid、status_recovery 以及 on Clause 列上添加一个 INDEX。
SELECT u.name,
u.email,
u.cpf,
u.register,
r.name AS role_name,
s.name AS sector_name,
b.name AS branch_name,
u.status
FROM users u
INNER JOIN roles r ON r.id = u.role_id
INNER JOIN sectors s ON s.id = u.sector_id
INNER JOIN branches b ON b.id = u.branch_id
WHERE u.status = 2
UNION
SELECT u.name,
u.email,
u.cpf,
u.register,
r.name AS role_name,
s.name AS sector_name,
b.name AS branch_name,
u.status
FROM users u
INNER JOIN roles r ON r.id = u.role_id
INNER JOIN sectors s ON s.id = u.sector_id
INNER JOIN branches b ON b.id = u.branch_id
WHERE EXISTS (
SELECT 1
FROM user_recovery ur
WHERE ur.user_id = u.id
AND ur.status_recovery = 1
)
【讨论】:
【参考方案2】:“我会看到你的 UNION;并为你提供一个派生表。”
SELECT u.name,
u.email,
u.cpf,
u.register,
r.name AS role_name,
s.name AS sector_name,
b.name AS branch_name,
u.status
FROM ( SELECT id
FROM users
WHERE status = 2
UNION DISTINCT -- or UNION ALL; see below
SELECT user_id
FROM user_recovery
WHERE status_recovery = 1 -- see new index
) AS u1
JOIN users AS u USING(id) -- self-join to pick up other columns
JOIN roles r ON r.id = u.role_id
JOIN sectors s ON s.id = u.sector_id
JOIN branches b ON b.id = u.branch_id;
索引:
user_recovery: INDEX(status_recovery, user_id) -- in this order
users: INDEX(status, id) -- in this order
(I assume `id` is the PRIMARY KEY in each table)
这里的一般规则是...当您有一堆 JOIN,但只有一个表控制哪些行,但那是混乱或缓慢的(例如,在这种情况下是 UNION,在其他情况下是 GROUP BY 或 LIMIT),
-
优化查找 id(user.id aka user_id)是最佳方式。
然后加入原始表(如果需要)以及其他表。
在所有这些过程中,user_recovery
的新索引显然可能是有益的。
(如果UNION ALL
不会产生任何重复,请切换到它以获得更快的速度。)
【讨论】:
以上是关于在 MySql 查询中替换“OR EXISTS”,这样我可以获得更好的性能结果的主要内容,如果未能解决你的问题,请参考以下文章
PostgreSQL对or exists产生的filter优化一
PostgreSQL对or exists产生的filter优化一
Subquery using Exists 1 or Exists *
Subquery using Exists 1 or Exists *