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 使用不同的列