意外的超长查询时间(使用嵌套 WHEN-IN 约 5 分钟)

Rya*_*ard 5 mysql performance normalization join

我第一次遇到 MySQL 查询执行时间过长(约 5 分钟)的问题。

数据库中的数据是高度(而非任意)规范化的。它非常有效地组织和改组数据以用于不同目的的许多非常有用的方式显示,除了这个特定的查询正在向它投掷扳手。

我无法理解这样做的原因。但是,一些背景信息可能会对其他任意复杂的查询有所了解。


该公司将世界划分为许多团队(macroregions)。根据他们的专业知识,每个人都属于一两个团队。

  • 例如,有许多不同的团队。几个例子是SpanishSaharaIberiaPortugueseJungle团队。每个团队都与其他团队有相当大的重叠,但在某种意义上是独立的。

  • Arabic团队与Sahara团队密切合作,因为数据库告诉他们由于地理位置重叠,他们必须在某些任务上一起工作。在SpanishPortuguese球队也紧密合作,他们与工作都AmericasEurope队和Portuguese队还与Africa球队一样,该Arabic团队。

  • 每个团队都有一组给定的区域,这些区域也不是该特定团队独有的。例如,该Mediterranean地区属于大约 12 个团队,当那里发生事件时,他们都会一起工作。

  • 每个国家属于一个或多个地区。Turkey属于Central AsiaEurope甚至Mediterranean,以及其他一些。

鉴于所有这些,有必要向每个人展示他们团队中的其他人正在做什么,以及不在他们的团队中但具有重叠区域的人。

查询 1完美地完成了这一点,而且非常快,不到 0.09 秒。


                SELECT report_name 
                FROM reports 
                WHERE region IN (
                    SELECT distinct region 
                    FROM macroregions
                    WHERE macroregion IN (
                        SELECT distinct macroregion
                        FROM users 
                        WHERE callsign = '$thisuser'
                    )
                ) 
Run Code Online (Sandbox Code Playgroud)

report_name当查询 1 认为登录的个人应该知道它时,人们可以看到 的每次出现也很重要。Reports_names为每个地理位置自动生成和重复使用

  • 如果我在East Asia团队中并且我所在地区的某人正在与Spanish团队合作,则数据库中将有两个条目:

    • 20120210JOMX01 Japan Okinawa

    • 20120210JOMX01 Mexico Nuevo Leon

查询 2接受查询 1WHEN-IN在其周围包裹一层s。它让East Asia团队成员知道他们团队中的某个人正在工作Mexico。但是性能延迟是不能接受的,完全不能用;完成一个查询需要将近5分钟!


        SELECT * 
        FROM reports
        WHERE report_name IN (
            SELECT report_name 
            FROM reports 
            WHERE region IN (
                SELECT distinct region 
                FROM macroregions
                WHERE macroregion IN (
                    SELECT distinct macroregion
                    FROM users 
                    WHERE callsign = '$thisuser'
                )
            )    
        )
Run Code Online (Sandbox Code Playgroud)

尽管查询有效,但它需要很长时间才能生效。同样,这个特定查询需要多长时间令人吃惊。而界面中的其他任何东西都没有给我带来任何性能问题(即它们都需要不到一秒钟)。

我可以采取哪些步骤来解决这个问题?

Rol*_*DBA 3

让我们从您的原始查询开始

   SELECT *  
    FROM reports 
    WHERE report_name IN ( 
        SELECT report_name  
        FROM reports  
        WHERE region IN ( 
            SELECT distinct region  
            FROM macroregions 
            WHERE macroregion IN ( 
                SELECT distinct macroregion 
                FROM users  
                WHERE callsign = '$thisuser' 
            ) 
        )     
    ) 
Run Code Online (Sandbox Code Playgroud)

您只能(分阶段)收集密钥,然后将密钥与reports表连接起来。这是我新提出的查询

SELECT reports.* FROM
(
    SELECT rpts.report_name FROM
    (
        SELECT DISTINCT regions.region FROM
        (SELECT DISTINCT macroregion
        FROM users WHERE callsign = '$thisuser') users
        INNER JOIN
        (SELECT DISTINCT macroregion,region FROM regions) regions
        USING (macroregion)
    ) regionkeys
    INNER JOIN
    (SELECT region,report_name FROM reports) rpts
    USING (region)
) reportnamekeys
INNER JOIN reports
USING (report_name);
Run Code Online (Sandbox Code Playgroud)

如果您对 USING 子句不满意,这里是我在不使用 USING 子句的情况下新提出的查询:

SELECT reports.* FROM
(
    SELECT rpts.report_name FROM
    (
        SELECT DISTINCT regions.region FROM
        (SELECT DISTINCT macroregion
        FROM users WHERE callsign = '$thisuser') users
        INNER JOIN
        (SELECT DISTINCT macroregion,region FROM regions) regions
        ON users.macroregion = regions.macroregion
    ) regionkeys
    INNER JOIN
    (SELECT region,report_name FROM reports) rpts
    ON regionkeys.region = rpts.region
) reportnamekeys
INNER JOIN reports
ON reportkeys.report_name = reports.report_name;
Run Code Online (Sandbox Code Playgroud)

您将需要一些索引来支持子查询

ALTER TABLE users   ADD INDEX callsign_macroregion_ndx (callsign,macroregion);
ALTER TABLE regions ADD INDEX macroregion_region_ndx (macroregion,region);
ALTER TABLE reports ADD INDEX region_report_name_ndx (region,report_name);
ALTER TABLE reports ADD INDEX report_name_ndx (report_name);
Run Code Online (Sandbox Code Playgroud)

前 3 个索引称为覆盖索引。之所以这样称呼它们,是因为子查询仅需要那些确切的列。因此,无需从表中读取。数据仅从索引中获取。

您还应该研究如何重构查询以收集键、尽早执行 WHERE 子句以及最后执行 JOIN。以下是有关如何执行此操作的精彩 YouTube 视频:http://youtu.be/ZVisY-fEoMw(基于本书:重构 SQL 应用程序

试一试 !!!

  • “USING”只是执行“NATURAL JOIN”的一种奇特的替代方法,只要被连接的两个表在具有相同数据类型的每个表中使用相同的字段名即可。 (3认同)