0

我们有一个相对简单的查询,它跨 4 个表执行 LEFT JOIN。A 是“主”表或层次结构中最顶层的表。B链接到A,C链接到B。此外,X链接到A。所以层次结构基本上是

A
C => B => A
X => A

查询本质上是:

SELECT
    a.*, b.*, c.*, x.*
FROM
    a
    LEFT JOIN b ON b.a_id = a.id
    LEFT JOIN c ON c.b_id = b.id
    LEFT JOIN x ON x.a_id = a.id
WHERE
    b.flag = true
ORDER BY
    x.date DESC
LIMIT 25

通过EXPLAIN,我已经确认正确的索引已经到位,并且内置的 MySQL 查询优化器正在正确和正确地使用这些索引

所以这是奇怪的部分......

当我们按原样运行查询时,运行大约需要 1.1 秒。

但是,在进行了一些检查之后,似乎如果我删除了大部分 SELECT 字段,我的速度就会得到显着提升。

所以如果我们把它变成一个两步查询过程:

  1. 第一个查询与上面相同,只是将 SELECT 子句更改为 onlySELECT a.id而不是SELECT *
  2. 第二个查询也与上面相同,除了将 WHERE 子句更改为仅对a.id IN查询 1 的结果执行一次,而不是我们之前的结果

结果是截然不同的。第一个查询是 0.03 秒,第二个查询是 0.02 秒。

在代码中执行这个两步查询本质上使我们的性能提高了 20 倍。

所以这是我的问题:

这种类型的优化不应该已经在数据库引擎中完成了吗?为什么实际选择的字段的差异会对查询的整体性能产生影响?

归根结底,它只是选择完全相同的 25 行并返回这 25 行的完全相同的完整内容。那么,为什么性能差距如此之大呢?

添加于太平洋夏令时间 2012 年 8 月 24 日下午 13:02

感谢 eggyal 和invertedSpear 的反馈。首先,这不是缓存问题——我已经在每种方法之间交替运行了多次(大约 10 次)两个查询的测试。第一个(单个查询)方法的结果平均为 1.1 秒,第二个(2 个查询)方法的结果平均为 0.03+0.02 秒。

在索引方面,我想我已经做了一个 EXPLAIN 来确保我们通过键,并且在大多数情况下我们是。但是,我只是再次快速检查了一下,还有一件有趣的事情需要注意:

较慢的“单一查询”方法不显示第三行的“使用索引”的额外注释:

+----+-------------+-------+--------+------------------------+-------------------+---------+-------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys          | key               | key_len | ref                           | rows | Extra                                        |
+----+-------------+-------+--------+------------------------+-------------------+---------+-------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | t1    | index  | PRIMARY                | shop_group_id_idx | 5       | NULL                          |  102 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | t2    | eq_ref | PRIMARY                | PRIMARY           | 4       | dbmodl_v18.t1.organization_id |    1 | Using where                                  |
|  1 | SIMPLE      | t0    | ref    | bundle_idx,shop_id_idx | shop_id_idx       | 4       | dbmodl_v18.t1.organization_id |  309 |                                              |
|  1 | SIMPLE      | t3    | eq_ref | PRIMARY                | PRIMARY           | 4       | dbmodl_v18.t0.id              |    1 |                                              |
+----+-------------+-------+--------+------------------------+-------------------+---------+-------------------------------+------+----------------------------------------------+

当我们仅查询 ID 时,它确实显示“使用索引”:

+----+-------------+-------+--------+------------------------+-------------------+---------+-------------------------------+------+----------------------------------------------+
| id | select_type | table | type   | possible_keys          | key               | key_len | ref                           | rows | Extra                                        |
+----+-------------+-------+--------+------------------------+-------------------+---------+-------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | t1    | index  | PRIMARY                | shop_group_id_idx | 5       | NULL                          |  102 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | t2    | eq_ref | PRIMARY                | PRIMARY           | 4       | dbmodl_v18.t1.organization_id |    1 | Using where                                  |
|  1 | SIMPLE      | t0    | ref    | bundle_idx,shop_id_idx | shop_id_idx       | 4       | dbmodl_v18.t1.organization_id |  309 | Using index                                  |
|  1 | SIMPLE      | t3    | eq_ref | PRIMARY                | PRIMARY           | 4       | dbmodl_v18.t0.id              |    1 |                                              |
+----+-------------+-------+--------+------------------------+-------------------+---------+-------------------------------+------+----------------------------------------------+

奇怪的是,两者都列出了正在使用的正确索引......但我想它引出了问题:

为什么它们不同(考虑到所有其他条款都完全相同)?这是否表明它为什么更慢?

EXPLAIN不幸的是,当“额外”列在结果中为空白/空时,MySQL 文档没有提供太多信息。

4

1 回答 1

1

比速度更重要的是,您的查询逻辑存在缺陷。当您在 WHERE 子句中测试 LEFT JOINed 列时(除了测试 NULL),您强制该连接表现得好像它是一个 INNER JOIN。相反,你会想要:

SELECT
    a.*, b.*, c.*, x.*
FROM
    a
    LEFT JOIN b ON b.a_id = a.id
        AND b.flag = true
    LEFT JOIN c ON c.b_id = b.id
    LEFT JOIN x ON x.a_id = a.id
ORDER BY
    x.date DESC
LIMIT 25

我的下一个建议是检查.*您的 SELECT 中的所有这些。您真的需要所有表中的所有列吗?

于 2012-08-24T20:17:12.623 回答