使用 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_date
、last_modification_date
(日期字段)
主表staff_node_staffnode
包含员工创建的所有数据(如产品、品牌等)的基本信息。它主要包含字段owner_id
(staff 表的外键,我在这里不详述)和is_verified
(布尔字段)和指向node_node
的外键staffnode_ptr_id
像product_merchandise
、product_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_id
是product_merchandise
的主键,node_ptr_id
是staff_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
上面的结果与我在原始帖子中所说的一致。
如果我尝试按sku
(product_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_idx
和node_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的主要内容,如果未能解决你的问题,请参考以下文章
mysql 优化慢复杂sql (多个left join 数量过大 order by 巨慢)