MySQL ORDER BY 跨多个连接表的列

Posted

技术标签:

【中文标题】MySQL ORDER BY 跨多个连接表的列【英文标题】:MySQL ORDER BY columns across multiple joined tables 【发布时间】:2017-08-09 18:42:16 【问题描述】:

我有一个由三个表组成的数据库,结构如下:

餐厅餐桌: restaurant_id、location_id、评级。 示例: 1325、77、4.5

restaurant_name 表: restaurant_id、语言、名称。 示例: 1325, 'en', 'Pizza Express'

location_name 表: location_id、语言、名称。 示例: 77, 'en', 'New York'

我想获取英文的餐厅信息,按位置名称和餐厅名称排序,并使用 LIMIT 子句对结果进行分页。所以我的 SQL 是:

SELECT ln.name, rn.name
FROM restaurant r
INNER JOIN location_name ln
ON r.location_id = ln.location_id
AND ln.language = 'en'
INNER JOIN restaurant_name rn
ON r.restaurant_id = rn.restaurant_id
AND rn.language = 'en'
ORDER BY ln.name, rn.name
LIMIT 0, 50

这非常慢 - 所以我使用延迟 JOIN 改进了我的 SQL,这让事情变得更快(从超过 10 秒到 2 秒):

SELECT ln.name, rn.name
FROM restaurant r
INNER JOIN (
    SELECT r.restaurant_id
    FROM restaurant r
    INNER JOIN location_name ln
    ON r.location_id = ln.location_id
    AND ln.language = 'en'
    INNER JOIN restaurant_name rn
    ON r.restaurant_id = rn.restaurant_id
    AND rn.language = 'en'
    ORDER BY ln.name, rn.name
    LIMIT 0, 50
) r1
ON r.restaurant_id = r1.restaurant_id
INNER JOIN location_name ln
ON r.location_id = ln.location_id
AND ln.language = 'en'
INNER JOIN restaurant_name rn
ON r.restaurant_id = rn.restaurant_id
AND rn.language = 'en'
ORDER BY ln.name, rn.name

不幸的是,2 秒对用户来说仍然不是很可接受,所以我去检查我的查询的 EXPLAIN,看起来慢的部分在 ORDER BY 子句上,我看到“使用临时;使用文件排序”。我查看了关于ORDER BY optimization 的官方参考手册,我发现了这个声明:

在某些情况下,mysql 无法使用索引来解析 ORDER BY, 虽然它可能仍然使用索引来查找匹配的行 WHERE 子句。例子:

查询连接了很多表,ORDER BY中的列没有 全部来自用于检索行的第一个非常量表。 (这是 EXPLAIN 输出中第一个没有 const 连接类型。)

因此,就我而言,鉴于我排序的两列来自非常量联接表,因此无法使用索引。我的问题是,我是否可以采取任何其他方法来加快速度,或者我到目前为止所做的已经是我能做到的最好的了?

提前感谢您的帮助!

编辑 1

下面是带有 ORDER BY 子句的 EXPLAIN 输出:

+----+-------------+------------+--------+--------------------------+-----------------------+---------+--------------------------------+------+----------------------------------------------+
| id | select_type | table      | type   | possible_keys            | key                   | key_len | ref                            | rows | Extra                                        |
+----+-------------+------------+--------+--------------------------+-----------------------+---------+--------------------------------+------+----------------------------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                     | NULL                  | NULL    | NULL                           |   50 |                                              |
|  1 | PRIMARY     | rn         | ref    | idx_restaurant_name_1    | idx_restaurant_name_1 | 1538    | r1.restaurant_id,const,const   |    1 | Using where                                  |
|  1 | PRIMARY     | r          | eq_ref | PRIMARY,idx_restaurant_1 | PRIMARY               | 4       | r1.restaurant_id               |    1 |                                              |
|  1 | PRIMARY     | ln         | ref    | idx_location_name_1      | idx_location_name_1   | 1538    | test.r.location_id,const,const |    1 | Using where                                  |
|  2 | DERIVED     | rn         | ALL    | idx_restaurant_name_1    | NULL                  | NULL    | NULL                           | 8484 | Using where; Using temporary; Using filesort |
|  2 | DERIVED     | r          | eq_ref | PRIMARY,idx_restaurant_1 | PRIMARY               | 4       | test.rn.restaurant_id          |    1 |                                              |
|  2 | DERIVED     | ln         | ref    | idx_location_name_1      | idx_location_name_1   | 1538    | test.r.location_id             |    1 | Using where                                  |
+----+-------------+------------+--------+--------------------------+-----------------------+---------+--------------------------------+------+----------------------------------------------+

下面是没有 ORDER BY 子句的 EXPLAIN 输出:

+----+-------------+------------+--------+--------------------------+-----------------------+---------+--------------------------------+------+--------------------------+
| id | select_type | table      | type   | possible_keys            | key                   | key_len | ref                            | rows | Extra                    |
+----+-------------+------------+--------+--------------------------+-----------------------+---------+--------------------------------+------+--------------------------+
|  1 | PRIMARY     | <derived2> | ALL    | NULL                     | NULL                  | NULL    | NULL                           |   50 |                          |
|  1 | PRIMARY     | rn         | ref    | idx_restaurant_name_1    | idx_restaurant_name_1 | 1538    | r1.restaurant_id,const,const   |    1 | Using where              |
|  1 | PRIMARY     | r          | eq_ref | PRIMARY,idx_restaurant_1 | PRIMARY               | 4       | r1.restaurant_id               |    1 |                          |
|  1 | PRIMARY     | ln         | ref    | idx_location_name_1      | idx_location_name_1   | 1538    | test.r.location_id,const,const |    1 | Using where              |
|  2 | DERIVED     | rn         | index  | idx_restaurant_name_1    | idx_restaurant_name_1 | 1538    | NULL                           | 8484 | Using where; Using index |
|  2 | DERIVED     | r          | eq_ref | PRIMARY,idx_restaurant_1 | PRIMARY               | 4       | test.rn.restaurant_id          |    1 |                          |
|  2 | DERIVED     | ln         | ref    | idx_location_name_1      | idx_location_name_1   | 1538    | test.r.location_id             |    1 | Using where; Using index |
+----+-------------+------------+--------+--------------------------+-----------------------+---------+--------------------------------+------+--------------------------+

编辑 2

下面是表格的 DDL。我构建它们只是为了说明这个问题,真正的表有更多的列。

CREATE TABLE restaurant (
  restaurant_id INT NOT NULL AUTO_INCREMENT,
  location_id INT NOT NULL,
  rating INT NOT NULL,
  PRIMARY KEY (restaurant_id),
  INDEX idx_restaurant_1 (location_id)
);

CREATE TABLE restaurant_name (
  restaurant_id INT NOT NULL,
  language VARCHAR(255) NOT NULL,
  name VARCHAR(255) NOT NULL,
  INDEX idx_restaurant_name_1 (restaurant_id, language),
  INDEX idx_restaurant_name_2 (name)
);

CREATE TABLE location_name (
  location_id INT NOT NULL,
  language VARCHAR(255) NOT NULL,
  name VARCHAR(255) NOT NULL,
  INDEX idx_location_name_1 (location_id, language),
  INDEX idx_location_name_2 (name)
);

【问题讨论】:

你的表有索引吗?即使没有帮助订单,他们仍然会加快您的查询速度。 @Jhorra 是的,在所有 _id 列以及 location_name 和 restaurant_name 表的 name 列上。不幸的是,两个名称列上的索引根本没有帮助,这是意料之中的,因为它与手册所说的一致。 这个查询是如何执行的?您正在执行 INNER JOIN location_name ln ON r.location_id = ln.location_id 但 r.location_id 甚至不是构成 r 数据集的 select 语句中可用的字段。 您能否分享您的三个表中每个表中的大致记录数?此外,您是否尝试运行查询,从查询和子查询中删除 order by 子句,以查看它的运行速度? 首先,正如@user681574 提到的,您的查询是错误的:内部查询不包含足够的字段以用于后续连接。因此,这可能不是您遵循的确切方法。此外,内部查询与您的原始查询完全一样(除了已从 50 更改为 30 的限制),它只是不返回两个字段(restaurant_name、location_name),您需要返回并询问相同的表再次获取它们。那么这种方法怎么能比第一种方法运行得快很多,而实际上它需要做更多的连接呢? 【参考方案1】:

根据EXPLAIN 的数字,可能有大约 170 个“页面”的餐厅 (8484/50)?我建议这对于分页是不切实际的。我强烈建议您重新考虑 UI。这样做,您所说的性能问题可能会消失。

例如,用户界面可以是 2 步而不是 170 步才能到达津巴布韦的餐馆。第一步,选择国家。 (好吧,这可能是国家/地区的第 5 页。)第 2 步,查看该国家/地区的餐馆列表;只需翻阅几页。对用户更好;对数据库更好。

附录

为了优化分页,从一个单个表中获取分页列表(这样你就可以“记住你离开的地方”)。 然后加入语言表以查找翻译。请注意,这只查找页面的翻译价值,而不是数千。

【讨论】:

感谢您的反馈瑞克。你是对的,有很多“页面”,但我没有明确表示。列表实现为无限滚动,如 Facebook 提要。这就是为什么我需要一些不同的排序顺序来简化用户的浏览。关于位置过滤器,它已经到位,但我仍然遇到这个问题。在一个国家/城市里真的可以有很多餐馆。坦率地说,我的网站并没有那么受欢迎,但在一个位置上仍然积累了超过 7000 个条目。 见过百科全书集吗? 20(左右)卷,标有“A-At”、“Au-Ce”、“Ch-Cu”等。一目了然,您就知道该抓哪一卷了。这是处理 170 次“下一步”点击的一种方法。另一种方法是拒绝超过 10 页。而是强迫用户提供更多细节。比如评级。或邮政编码。或者按纬度和经度将其分成可管理的块。或城市区域(正常运行时间、停机时间、林肯公园、霍姆伍德、海特区、金融区)。 无限滚动中的 7K 项将使您的页面“不那么受欢迎”。让它成为一个学习练习。尝试我提到的一些想法。把它们扔掉并发明新的。您的旅程可能是一次激动人心的冒险。 好吧,除非你说 TripAdvisor 也“不那么受欢迎”,否则我看不出有很多页面有什么问题? TripAdvisor 每页显示 20 家餐厅,其中日本有 89 页 - tripadvisor.com/…。它提供了两种排序选项 - 按受欢迎程度和按字母顺序。 不要误会我的意思——我并不是说 TripAdvisor 的设计是最好的,但我只是想证明它是可行的。回到我原来的问题,我想从技术上知道如何实现这一点。我假设一个上下文只是为了说明。如果您将此视为纯粹的数据库调整问题 - 我有几个表要加入,我想对每个加入表中的一些列进行排序。有没有办法让它更快?

以上是关于MySQL ORDER BY 跨多个连接表的列的主要内容,如果未能解决你的问题,请参考以下文章

优化 MySQL - WHERE 和 ORDER BY 使用不同的列

mysql 优化慢复杂sql (多个left join 数量过大 order by 巨慢)

跨多个表的列的 SQL 唯一约束

mysql order by多个字段

sql 数据量很大 ,为啥加了order by速度变慢了?

mysql order by怎样多个时间字段排序