在 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 *

Oracle“优化” OR + IN 到 OR + EXISTS,这非常慢

mySQL怎么批量替换查询结果中的字段值?