使用 JOIN 优化 SQL 查询的 ORDER BY 和 WHERE

Posted

技术标签:

【中文标题】使用 JOIN 优化 SQL 查询的 ORDER BY 和 WHERE【英文标题】:Optimizing ORDER BY and WHERE on sql queries with JOIN 【发布时间】:2021-08-25 01:34:29 【问题描述】:

我目前正在从事在线电子商务平台后台工作。 我目前有大约 70 000 种产品,我想加快数据的显示速度,以便员工可以更高效地工作。

我正在使用 mysql“Ver 14.14 Distrib 5.7.28”。

基本上对于我的后台(我不会明确列出列的详细信息,因为我认为这并不重要),我有:

主表 node_node 包含所有数据的基本信息,例如 creation_datelast_modification_date(日期字段) 主表staff_node_staffnode 包含员工创建的所有数据(如产品、品牌等)的基本信息。它主要包含字段owner_id(staff 表的外键,我在这里不详述)和is_verified(布尔字段)和指向node_node 的外键staffnode_ptr_idproduct_merchandiseproduct_brand这样的数据结构表,它们包含自己的字段和指向staff_node_staffnode的外键staffnode_ptr_id

我首先运行一个查询来检索我想要显示的产品的所有 ID(考虑到大量数据,我更喜欢首先只检索我的列表中产品的 ID,每页限制为 30 个,并且然后在这个子集上检索更多数据,并在其他表上进行更多连接)

SELECT id from product_merchandise pm 
INNER JOIN staff_node_staffnode sns ON sns.node_ptr_id = pm.staffnode_ptr_id 
INNER JOIN node_node nn ON nn.id = sns.node_ptr_id 
ORDER BY creation_date DESC LIMIT 30;

product_merchandise(staffnode_ptr_id)staff_node_staffnode(node_ptr_id)node_node(id) 上有一个索引。 运行此查询平均需要 2 到 3 秒,这太长了。

编辑:正如 cmets 中所建议的,这里是 EXPLAIN 查询的输出。 EXPLAIN ANALYZE 不适用于我的 Mysql 版本。

+----+-------------+-------+------------+--------+---------------+------------------------------+---------+------------------------+-------+----------+----------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys | key                          | key_len | ref                    | rows  | filtered | Extra                                        |
+----+-------------+-------+------------+--------+---------------+------------------------------+---------+------------------------+-------+----------+----------------------------------------------+
|  1 | SIMPLE      | pm    | NULL       | index  | PRIMARY       | product_merchandise_447d3092 | 5       | NULL                   | 69623 |   100.00 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | sns   | NULL       | eq_ref | PRIMARY       | PRIMARY                      | 4       | db.pm.staffnode_ptr_id |     1 |   100.00 | Using index                                  |
|  1 | SIMPLE      | nn    | NULL       | eq_ref | PRIMARY       | PRIMARY                      | 4       | db.pm.staffnode_ptr_id |     1 |   100.00 | NULL                                         |
+----+-------------+-------+------------+--------+---------------+------------------------------+---------+------------------------+-------+----------+----------------------------------------------+

我决定在node_node(creation_date) 上添加一个索引creation_date_idx,当我强制使用它时,我得到了0.10s 到0.15s 之间,这是完美的:

SELECT id from product_merchandise pm 
INNER JOIN staff_node_staffnode sns ON sns.node_ptr_id = pm.staffnode_ptr_id 
INNER JOIN node_node nn FORCE INDEX(creation_date_idx) ON nn.id = sns.node_ptr_id 
ORDER BY creation_date DESC LIMIT 30;

现在的问题是,做产品的工作人员应该可以根据不同的参数进行过滤,比如owner_id

SELECT id from product_merchandise pm 
INNER JOIN staff_node_staffnode sns ON sns.node_ptr_id = pm.staffnode_ptr_id 
INNER JOIN node_node nn FORCE INDEX(creation_date_idx) ON nn.id = sns.node_ptr_id 
WHERE sns.owner_id = [NUMBER]
ORDER BY creation_date DESC LIMIT 30;

结果很糟糕(我在 30 秒左右停止了查询,但我认为这可能需要更多时间),这是有道理的,因为我强制使用与此处无关的索引 creation_date_index

如果我不使用这个索引,我会得到更好的结果(1-2 秒),但我又回到了第一个问题:计算时间太长。

编辑:按照建议,这里是 EXPLAIN for 的输出

SELECT id from product_merchandise pm 
INNER JOIN staff_node_staffnode sns ON sns.node_ptr_id = pm.staffnode_ptr_id 
INNER JOIN node_node nn ON nn.id = sns.node_ptr_id 
WHERE sns.owner_id = [NUMBER]
ORDER BY creation_date DESC LIMIT 30;
+----+-------------+-------+------------+--------+---------------------------------------+------------------------------+---------+------------------------+-------+----------+----------------------------------------------+
| id | select_type | table | partitions | type   | possible_keys                         | key                          | key_len | ref                    | rows  | filtered | Extra                                        |
+----+-------------+-------+------------+--------+---------------------------------------+------------------------------+---------+------------------------+-------+----------+----------------------------------------------+
|  1 | SIMPLE      | pm    | NULL       | index  | PRIMARY                               | product_merchandise_447d3092 | 5       | NULL                   | 69220 |   100.00 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | sns   | NULL       | eq_ref | PRIMARY,staff_node_staffnode_5e7b1936 | PRIMARY                      | 4       | db.pm.staffnode_ptr_id |     1 |    19.00 | Using where                                  |
|  1 | SIMPLE      | nn    | NULL       | eq_ref | PRIMARY                               | PRIMARY                      | 4       | db.pm.staffnode_ptr_id |     1 |   100.00 | NULL                                         |
+----+-------------+-------+------------+--------+---------------------------------------+------------------------------+---------+------------------------+-------+----------+----------------------------------------------+

我想我应该创建另一个索引,但我真的不知道在哪些列上。 此外,工作人员应该能够过滤 5 个不同的字段(假设它们都是 VARCHAR 或 FOREIGN KEY 或 BOOLEAN),并按这些不同的字段排序。这些字段可能来自表product_merchandise(例如product_name)或staff_node_staffnode(创建者或is_verified)或事件node_node(例如creation_date)。

我希望我说得够清楚。 感谢您的宝贵时间,我将不胜感激!

祝你有美好的一天。

【问题讨论】:

你必须为你的慢查询分享你的执行计划 感谢您的回答。执行计划是什么意思?你想让我使用 EXPLAIN sql 函数吗? 是的EXPLAIN ANALYZE 好的,我按照你的建议添加了 EXPLAIN。 EXPLAIN ANALYZE 不适用于我拥有的 MySQL 版本(Ver 14.14 Distrib 5.7.28)。此外,我只在第一个查询中使用了 EXPLAIN,而不是超过 30 秒的查询,因为我认为它不相关。 请为表格提供SHOW CREATE TABLE。然后我们可以讨论您可能需要哪些额外的索引。 【参考方案1】:

我把它放在这里是因为它不适合 cmets , 以下是提高性能所需的索引列表:

product_merchandise(id,staffnode_ptr_id) staff_node_staffnode(node_ptr_id,owner_id) node_node(id,creation_date DESC)

将您的索引更改/添加到上面的列表中,让我们看看它如何改变性能

【讨论】:

好的,非常感谢你,我会测试这个并用结果发布我的答案【参考方案2】:

感谢 eshirvana 的建议。 我发布答案而不是编辑我的原始问题,因为我的测试结果很长。我希望这不会是一个问题。

首先我忘了说staffnode_ptr_idproduct_merchandise的主键,node_ptr_idstaff_node_staffnode的主键。

那么这里是我除了 PRIMARY 索引之外的索引:

CREATE INDEX node_creationdate_idx ON node_node(creation_date);
CREATE INDEX node_id_creationdate_idx ON node_node(id,creation_date);
CREATE INDEX staffnode_nodeptrid_ownerid_idx ON staff_node_staffnode(node_ptr_id,owner_id);

我没有为索引 node_id_creationdate_idx 指定 DESC,因为根据具体情况排序可能是 ASC 或 DESC。

这是我运行的速度测试的结果(我对每种情况执行了 10 次查询):

The details can be found on this link

No index forced, ordering by 'creation_date' only
average: 2.4473010037094354 fastest: 2.0254166573286057 slowest: 2.891202986240387

Forcing index 'node_creationdate_idx', ordering by 'creation_date' only
average: 0.045951709523797034 fastest: 0.03917844220995903 slowest: 0.06625311821699142

No index forced, ordering by 'creation_date' and filtering on 'owner_id'
average: 1.7595138054341077 fastest: 1.08128846809268 slowest: 2.858897101134062

Forcing index 'node_creationdate_idx', ordering by 'creation_date' and filtering on 'owner_id'
average: infinity

上面的结果与我在原始帖子中所说的一致。

如果我尝试按skuproduct_merchandise 表的 VARCHAR 列)排序,无论如何计算都非常快

No index forced, ordering by 'sku' only
average: 0.0022248398512601853 fastest: 0.0017771385610103607 slowest: 0.0032510906457901

No index forced, ordering by 'sku' and filtering on 'owner_id'
average: 0.00639396645128727 fastest: 0.0025643371045589447 slowest: 0.0197000615298748

在下面的结果中,我尝试强制使用新索引staffnode_nodeptrid_ownerid_idxnode_id_creationdate_idx

Forcing index 'staffnode_nodeptrid_ownerid_idx', ordering by 'creation_date' only
average: 2.1846631478518246 fastest: 1.665839608758688 slowest: 2.5894345454871655

Forcing index 'staffnode_nodeptrid_ownerid_idx', ordering by 'creation_date' and filtering on 'owner_id'
average: 0.9459988728165627 fastest: 0.726978026330471 slowest: 1.1611059792339802

Forcing index 'node_id_creationdate_idx', ordering by 'creation_date' only
average: 1.7628929097205401 fastest: 1.5384734570980072 slowest: 1.9222845435142517

Forcing index 'node_id_creationdate_idx', ordering by 'creation_date' and filtering on 'owner_id'
average: 1.2311949148774146 fastest: 0.9017647355794907 slowest: 1.4749027229845524

Forcing indexes 'node_id_creationdate_idx' and 'staffnode_nodeptrid_ownerid_idx', ordering by 'creation_date' only
average: 1.5638799782842399 fastest: 1.3537045568227768 slowest: 1.8629941195249557

Forcing indexes 'node_id_creationdate_idx' and 'staffnode_nodeptrid_ownerid_idx', ordering by 'creation_date' and filtering on 'owner_id'
average: 1.6410113696008921 fastest: 1.2819141708314419 slowest: 2.3169863671064377

总结:

使用这些索引我得到了稍微好一点的结果,尽管我认为它仍然太长了 似乎问题在于creation_date 不属于表product_merchandise,因此对其进行索引并不是很有效

你有什么建议?我应该更改表的结构吗?

感谢您的帮助!

【讨论】:

以上是关于使用 JOIN 优化 SQL 查询的 ORDER BY 和 WHERE的主要内容,如果未能解决你的问题,请参考以下文章

记一次join + order by 的sql优化

记一次join + order by 的sql优化

记一次join + order by 的sql优化

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

使用 ORDER BY 和 INNER JOIN 优化 MySQL 查询(选择用户关注的位置)

如何优化这个简单的 JOIN+ORDER BY 查询?