通过示例提高查询性能

Improving query performance by example

我正在尝试想出一种方法来改进查询,所使用的架构如下所示:

CREATE TABLE `orders` (
  `id` int PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `store_id` INTEGER NOT NULL,
  `billing_profile_id` INTEGER NOT NULL,
  `billing_address_id` INTEGER NULL,
  `total` DECIMAL(8, 2) NOT NULL
);

CREATE TABLE `billing_profiles` (
  `id` int PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `name` TEXT NOT NULL
);

CREATE TABLE `billing_addresses` (
  `id` int PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `address` TEXT NOT NULL
);

CREATE TABLE `stores` (
  `id` int PRIMARY KEY NOT NULL AUTO_INCREMENT,
  `name` TEXT NOT NULL
);

我正在执行的查询:

SELECT bp.name,
       ba.address,
       s.name,
       Sum(o.total) AS total
FROM   billing_profiles bp,
       stores s,
       orders o
       LEFT JOIN billing_addresses ba
              ON o.billing_address_id = ba.id
WHERE  o.billing_profile_id = bp.id
       AND s.id = o.store_id
GROUP  BY bp.name,
          ba.address,
          s.name;

这里是 EXPLAIN:

+----+-------------+-------+------------+--------+---------------+---------+---------+------------------------------+-------+----------+--------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref                          | rows  | filtered | Extra                                      |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------------------+-------+----------+--------------------------------------------+
|  1 | SIMPLE      | bp    | NULL       | ALL    | PRIMARY       | NULL    | NULL    | NULL                         |155000 |   100.00 | Using temporary                            |
|  1 | SIMPLE      | o     | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                         |220000 |    33.33 | Using where; Using join buffer (hash join) |
|  1 | SIMPLE      | ba    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | factory.o.billing_address_id |    1  |   100.00 | NULL                                       |
|  1 | SIMPLE      | s     | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | factory.o.store_id           |    1  |   100.00 | NULL                                       |
+----+-------------+-------+------------+--------+---------------+---------+---------+------------------------------+------+----------+--------------------------------------------+

我面临的问题是这个查询需要 30 多秒才能执行,我们有超过 200000 个订单,还有 150000+ billing_profiles/billing_addresses.

关于 index/constraints 我应该怎么做才能使此查询执行得更快?

编辑: 在评论中提出一些建议后,我将查询编辑为:

SELECT bp.name,
       ba.address,
       s.name,
       Sum(o.total) AS total
FROM   orders o
       INNER JOIN billing_profiles bp
               ON o.billing_profile_id = bp.id
       INNER JOIN stores s
               ON s.id = o.store_id
       LEFT JOIN billing_addresses ba
              ON o.billing_address_id = ba.id
GROUP  BY bp.name,
          ba.address,
          s.name; 

但是还是太费时间了。

我过去使用过并在许多情况下对 MySQL 有帮助的一件事是使用 STRAIGHT_JOIN 子句,它告诉引擎按照列出的顺序执行查询。

我已将您的查询清理到正确的 JOIN 上下文。由于 ORDER table 是数据的主要基础,而其他 3 个是对其各自 ID 的查找引用,因此我将 ORDER table 放在第一位。

SELECT STRAIGHT_JOIN
        bp.name,
        ba.address,
        s.name,
        Sum(o.total) AS total
    FROM
        orders o
            JOIN stores s
                ON o.store_id = s.id
            JOIN billing_profiles bp
                on o.billing_profile_id = bp.id
            LEFT JOIN billing_addresses ba
                ON o.billing_address_id = ba.id
    GROUP BY 
        bp.name,
        ba.address,
        s.name

现在,您的数据 table 看起来没有那么大,但是如果您要按顺序 table 中的 3 列进行分组,我会在基础上创建一个索引它们的基础是链接到其他 table 的“ID”键。添加总计以帮助覆盖索引/聚合查询,我将在

上建立索引
( store_id, billing_profile_id, billing_address_id, total )

我敢肯定,在现实中,您有许多其他列与订单相关联并且只是显示此查询的上下文。然后,我会更改为预查询,以便通过 ID 键对订单 table 完成一次聚合,然后将结果加入查找 tables,你只需要申请最终输出的 ORDER BY 子句。有点像..

SELECT
        bp.name,
        ba.address,
        s.name,
        o.total
    FROM
        ( select
                store_id,
                billing_profile_id,
                billing_address_id,
                sum( total ) total 
            from
                orders 
            group by
                store_id,
                billing_profile_id,
                billing_address_id ) o
    JOIN stores s
        ON o.store_id = s.id
    JOIN billing_profiles bp
        on o.billing_profile_id = bp.id
    LEFT JOIN billing_addresses ba
        ON o.billing_address_id = ba.id
    ORDER BY 
        bp.name,
        ba.address,
        s.name

将此索引添加到o,确保以billing_profile_id:

开头
INDEX(billing_profile_id, store_id, billing_address_id, total)

解释的讨论:

  • 优化器发现它需要对 一些 table.
  • 进行全面扫描
  • bpo 小,所以它选择 bp 作为“第一个”table。
  • 然后它反复进入下一个table。
  • 它没有看到 suitable 索引(一个以 billing_profile_id 开头的索引)并决定做“使用连接缓冲区(散列连接)”,这涉及加载整个 table 到 RAM 中的散列。
  • “使用临时”,虽然在“第一个”table 中提到,但直到 GROUP BY 之前才真正出现。 (GROUP BY引用了多个table,没办法优化。)

潜在错误 请检查 Sum(o.total) AS total 的结果。它在之后所有JOINing之前GROUP BY执行,所以它可能 膨胀。请注意 DRapp 的公式如何在 SUM 之前 JOIN。