用三个联接查询非常慢

时间:2019-03-17 12:02:58

标签: mysql sql pdo slim

我想返回所有在特定matches踢足球的国家({1)}。下表中定义了数据:

竞争

date

competition_seasons

id | country_id | name 
50       1         Premier League

competition_rounds

id | competition_id | name
 70       50          2019

匹配

id | season_id | name 
 58       70      Regular Season

id | round_id | home | away | result | datetime 44 58 22 87 1 - 0 2019-03-16:00:00 表中存储了不同的比赛,然后每个比赛可以有多个competition存储在season中。 competition_seasons也可以有不同的竞争者season,这些竞争者存储在rounds中。

所有competition_rounds都存储在matches表中,并按match分组。

我为API编写了此方法:

round_id

有成千上万个按ID进行组织的记录,但是查询花费了大约6、7秒才能返回在指定日期播放的所有$app->get('/country/get_countries/{date}', function (Request $request, Response $response, array $args) { $start_date = $args["date"] . " 00:00"; $end_date = $args["date"] . " 23:59"; $sql = $this->db->query("SELECT n.* FROM country n LEFT JOIN competition c ON c.country_id = n.id LEFT JOIN competition_seasons s ON s.competition_id = c.id LEFT JOIN competition_rounds r ON r.season_id = s.id LEFT JOIN `match` m ON m.round_id = r.id WHERE m.datetime BETWEEN '" . $start_date . "' AND '" . $end_date . "' GROUP BY n.id"); $sql->execute(); $countries = $sql->fetchAll(); return $response->withJson($countries); });

如何优化此过程?

性能

enter image description here

更新

如果我这样做,我会发现一件有趣的事情:

countries

查询的速度非常快,所以我猜SELECT round_id, DATE("2019-03-18") FROM `match` 字段会使连接部分变慢,对此有任何想法吗?

表结构

datetime

2 个答案:

答案 0 :(得分:4)

首先,将查询写为:

SELECT n.*
FROM country n JOIN
     competition c
     ON c.country_id = n.id JOIN
     competition_seasons s
     ON s.competition_id = c.id JOIN
     competition_rounds r
     ON r.season_id = s.id JOIN
     `match` m
     ON m.round_id = r.id
WHERE m.datetime >= ? AND
      m.datetime < ?
GROUP BY n.id;

此处的更改相对较小,不会影响性能。但是它们很重要:

  • JOIN而不是LEFT JOIN,因为您需要条件匹配。
  • 日期的参数而不是查询字符串,因为这是个好主意。
  • >=<进行比较,因为它同时适用于日期和日期时间。您将需要在结束日期前增加1天,但不包括时间部分。

然后,为了提高性能,您需要索引:

  • match(datetime, round_id)
  • competition_rounds(id, season_id)
  • competition_seasons(id, competition_id)
  • competition(id, country_id)
  • country(id)

实际上,第一个是最重要的。如果将各自的id列声明为主键,则不需要最后四个。

答案 1 :(得分:1)

使用LEFT JOIN,只能从上至下执行查询,这意味着将扫描最后一个表以查找之前表中条目的每个乘积。另外,使用LEFT JOINGROUP BY而不进行任何汇总都没有意义,因为它将始终返回所有国家/地区ID。话虽如此,我会这样重写它:

SELECT DISTINCT
    c.country_id
FROM 
    competition c,
WHERE 

    EXISTS (
        SELECT 
            *
        FROM
            competition_seasons s,
            competition_rounds r,
            `match` m
        WHERE
            s.competition_id = c.id
            AND r.season_id = s.id
            AND m.round_id = r.id 
            AND m.datetime BETWEEN ...
    )

我所知道的所有RDB都将正确地对此进行优化。 请注意,(match.datetime, match.round_id)上的2列索引(按此顺序)将对性能产生巨大影响。还是担心写入速度,建议至少在(match.datetime)上使用单个列索引。

有关字符串索引的重要提示:在RDB中,字符串比较总是古怪的。确保为datetime列使用二进制排序规则或使用本机DATETIME格式。各种RDB可能无法在不区分大小写的列上使用索引。

请注意,我删除了n上的联接-只是添加了另一个PK查找来检查国家/地区表中仍然存在该国家/地区。如果没有任何ON DELETE CASCADE或其他可确保数据一致性的约束,则可以重新添加它,例如:

SELECT DISTINCT
    n.id
FROM 
    country n
WHERE 

    EXISTS (
        SELECT 
            *
        FROM
            competition c,
            competition_seasons s,
            competition_rounds r,
            `match` m
        WHERE
            c.country_id=n.id
            AND s.competition_id = c.id
            AND r.season_id = s.id
            AND m.round_id = r.id 
            AND m.datetime BETWEEN ...
    )