MySQL 中的慢速内部连接与子查询

Slow inner joins with subqueries in MySQL

我遇到了一个缓慢的 MySQL 查询问题 (MySQL 5+)。让我们想想三个 tables:

customers:
- id_customer : int (PRIMARY)
- name        : varchar(255)

customers_addresses:
- id_customers_addresses : int (PRIMARY)
- id_customer : int (INDEX)
- street : varchar(255)
- zipcode : varchar(255)
- city : varchar(255)

customers_contacts:
- id_customers_contacts : int (PRIMARY)
- id_customer : int (INDEX)
- type : varchar(255)
- value : varchar(255)

现在,我的目标是在一个查询中收集所有地址和联系信息,每个客户一行。我的第一次尝试是使用 LEFT JOINs,因为一些客户没有任何地址 and/or 联系信息:

SELECT customers.id_customer,
       customers.name,
       X.contact AS contact,
       Y.street,
       Y.zipcode,
       Y.city
FROM customers
LEFT JOIN
(
  SELECT
    GROUP_CONCAT( CONCAT( type, ': ', value ) SEPARATOR ', ' ) AS contact,
    id_customer
  FROM customers_contacts
  GROUP BY id_customer
) AS X
ON X.id_customer = customers.id_customer

LEFT JOIN
(
  SELECT
    GROUP_CONCAT(street SEPARATOR '<br>' ) AS street,
    GROUP_CONCAT(zipcode SEPARATOR '<br>' ) AS zipcode,
    GROUP_CONCAT(city SEPARATOR '<br>' ) AS city,
    id_customer
  FROM customers_addresses
  GROUP BY id_customer
) AS Y
ON Y.id_customer = customers.id_customer
WHERE Y.street LIKE '%Avenue%'
ORDER BY customers.name DESC
LIMIT 0, 20

此查询用了 130 多秒才完成(每个 table 约 7000 个条目),这很不理想。

前置 EXPLAIN EXTENDED 给出:

id  select_type table               type    possible_keys   key            key_len     ref     rows    filtered    Extra
1   PRIMARY     customers           ref     name            name           3           const   4334    100.00      Using where; Using temporary; Using filesort
1   PRIMARY     <derived2>          ALL     NULL            NULL           NULL        NULL    7793    100.00
1   PRIMARY     <derived3>          ALL     NULL            NULL           NULL        NULL    8580    100.00      Using where
3   DERIVED     customers_addresses index   NULL            id_customer    5           NULL    8651    100.00
2   DERIVED     customers_contacts  index   NULL            id_customer    4           NULL    9314    100.00

我阅读了一些 Whosebug 帖子和 MySQL 文档。两者都说 INNER JOIN 快得多。我试图通过使用 UNION ALL:

来复制 LEFT JOININNER JOIN 的行为
SELECT customers.id_customer,
       customers.name,
       X.contact AS contact,
       Y.street,
       Y.zipcode,
       Y.city
FROM customers
INNER JOIN
(
  SELECT
    GROUP_CONCAT( CONCAT( type, ': ', value ) SEPARATOR ', ' ) AS contact,
    id_customer
  FROM customers_contacts
  GROUP BY id_customer
  UNION ALL
  SELECT
    '' AS contact,
    id_customer
  FROM customers
  WHERE id_customer NOT IN (SELECT DISTINCT id_customer FROM customers_contacts)
) AS X
ON X.id_customer = customers.id_customer

INNER JOIN
(
  SELECT
    GROUP_CONCAT(street SEPARATOR '<br>' ) AS street,
    GROUP_CONCAT(zipcode SEPARATOR '<br>' ) AS zipcode,
    GROUP_CONCAT(city SEPARATOR '<br>' ) AS city,
    id_customer
  FROM customers_addresses
  GROUP BY id_customer
  UNION ALL
  SELECT
    '' AS street,
    '' AS zipcode,
    '' AS city,
    id_customer
  FROM customers
  WHERE id_customer NOT IN (SELECT DISTINCT id_customer FROM customers_addresses)
) AS Y
ON Y.id_customer = customers.id_customer
WHERE Y.street LIKE '%Avenue%'
ORDER BY customers.name DESC
LIMIT 0, 20

此查询将性能提高了 20 秒。但是110秒还是不行table.

前置 EXPLAIN EXTENDED:

id   select_type         table               type           possible_keys      key      key_len ref         rows    filtered    Extra
1    PRIMARY             <derived2>          ALL            NULL               NULL     NULL    NULL        8596    100.00      Using temporary; Using filesort
1    PRIMARY             <derived5>          ALL            NULL               NULL     NULL    NULL        8604    100.00      Using join buffer
1    PRIMARY             customers           eq_ref         PRIMARY,name,name3 PRIMARY  4       Y.id_kunde  1       100.00      Using where
5    DERIVED             customers_addresses index          NULL               id_kunde 5       NULL        8651    100.00
6    UNION               customers           index          NULL               name2    767     NULL        8677    100.00      Using where; Using index
7    DEPENDENT SUBQUERY  customers_addresses index_subquery id_kunde           id_kunde 5       func        2       100.00      Using index
NULL UNION RESULT        <union5,6>          ALL            NULL               NULL     NULL    NULL        NULL    NULL
2    DERIVED             customers_contacts  index          NULL               id_kunde 4       NULL        10411   100.00
3    UNION               customers           index          NULL               name2    767     NULL        8677    100.00      Using where; Using index
4    DEPENDENT SUBQUERY  customers_contacts  index_subquery id_kunde           id_kunde 4       func        1       100.00      Using index
NULL UNION RESULT        <union2,3>          ALL            NULL               NULL     NULL    NULL        NULL    NULL

所以这是我的问题:如何改进这些查询之一 and/or 数据库 tables 以获得超快速响应?我不仅对解决方案感兴趣,而且对将来如何防止此类性能下降的策略感兴趣。

此致。

作为此处适用的一般规则,您可以说以下内容:

每当您使用连接 select 结果(子查询)的查询时,MySQL 必须先 运行 这些子查询,然后从 table结果。你这样做了两次,这意味着 MySQL 首先创建 2 tables,只有在结果完成后才删除它们。通过 MySQL 的适当内存管理,这是在内存中完成的。但是这些 table 是在没有索引的情况下创建的,因为 MySQL 无法神奇地确定哪个索引最适合这些派生的 table,并且因为它们通常是在内存中创建的,所以查询这些非常快(不如使用键的 SELECT 快)。

然后,当两个 table 完成后,MySQL 必须将您原来的 table 与两者结合起来,并即时创建第三个 table需要根据您的条件进行过滤和排序。

这是一个性能杀手。您的要求存在的问题之一是每个客户只能产生一行。这不是数据库保存信息的方式,因此您在 运行 时间内为数据转换(您的 GROUP_CONCAT 语句)付出了代价。我不是 100% 确定当前的 MySQL 数据库引擎对 UNION 语句做了什么,所以我不想对这些发表评论。

在可用键上使用简单的 INNER JOIN,但当结果是多个地址时,会为一个客户生成多行,您会发现性能大幅提升。您可以轻松地遍历编程语言层中的客户,一次请求一个客户的地址,如果您不愿意table 将所有客户和所有关联地址的结果拆分为该层上的客户。

TL;DR:放弃您的要求或忍受开销。