细化这个 MySQL 查询?
Posted
技术标签:
【中文标题】细化这个 MySQL 查询?【英文标题】:Refinement to this MySQL query? 【发布时间】:2013-09-25 11:03:04 【问题描述】:我有一个查询需要很长时间,我想知道是否有更好的方法?也许有连接?
目前大约需要 2.5 秒,这太长了。
稍微解释一下结构:我有产品、“主题”和“类别”。可以为产品分配任意数量的主题或类别。 themeitems 和 categoryitems 表是用于将类别/主题 ID 链接到产品 ID 的链接表。
我想获取包含至少一个主题和类别的所有产品的列表。我现在得到的查询如下:
SELECT *
FROM themes t, themeitems ti, products p, catitems ci, categories c
WHERE t.ID = ti.THEMEID
AND ti.PRODID = p.ID
AND p.ID = ci.PRODID
AND ci.CATID = c.ID
我实际上只是在执行查询时选择了我需要的行,但我已将其删除以抽象一点。
在正确方向上的任何帮助都会很棒!
编辑:在下面解释
【问题讨论】:
你能发布解释吗? 是的,已编辑问题。 返回多少行? 【参考方案1】:使用正确的 JOIN 并确保 JOIN 中使用的字段有索引是此问题的标准响应。
SELECT *
FROM themes t
INNER JOIN themeitems ti ON t.ID = ti.THEMEID
INNER JOIN products p ON ti.PRODID = p.ID
INNER JOIN catitems ci ON p.ID = ci.PRODID
INNER JOIN categories c ON ci.CATID = c.ID
JOIN 的规范有助于查询引擎确定它需要做什么,并且连接中使用的列上的索引将实现更快速的连接。
【讨论】:
“JOIN 的规范有助于查询引擎”——不是。优化器以相同的方式执行您和 OP 的查询(准确地说 - 一个在评估之前被重写为另一个) JOIN 有利于可读性(并确保不会忘记添加连接条件。)但我同意,这里缺少索引。 另外,枚举所需的索引也是有意义的。 @zerkms 我不完全同意你的看法。如果曾经从逻辑上想到加入,而他们按照他所写的方式,那么就会有所不同,并且加入似乎更快。在 OP 的查询中,所有表在被过滤之前都是笛卡尔式的,但在连接中,它们对每个连接都进行过滤,因此使用更少的内存来获得最终结果集。另外,为什么不实践世界标准,当连接更具可读性并提供相同或更好的性能时。 @Sumit Gupta:“我并不完全同意你的看法”——这就是优化器的工作方式。在查询评估之前,显式内部连接被重写为隐式连接。我没有说任何关于可读性的内容,而是关于性能的。 “在 OP 的查询中,所有表在被过滤之前都是笛卡尔的”——不是。查询的外观与它的执行方式无关。如有必要,优化器甚至可以更改连接顺序(并将一些JOIN
ed 表与 FROM
ed 表交换)【参考方案2】:
您的查询很慢,因为您的表上没有任何索引。
试试:
create unique index pk on themes (ID)
create index fk on themeitems(themeid, prodid)
create unique index pk on products (id)
create index fk catitems(prodid, catid)
create unique index pk on categories (id)
正如@symcbean 在 cmets 中所写,catitems 和 themeitems 索引也应该是唯一索引 - 如果没有其他列要添加到该索引(例如“validityDate”),请将其添加到 create 语句中。
【讨论】:
根本不应该改变数据库结构。有没有与此相反的情况,所以我可以在有无的情况下对其进行测试? @Michael 添加索引不是改变结构,它对表的字段和结构没有任何不同,只是将每条记录的信息分开存储,与书的索引相同。如果索引存在与否,书的内容保持不变[从技术上讲,它只是改变了日期的存储方式,但你现在可以忽略它]。 不确定我是否理解您的问题 themeitems 和 catitems 表上的索引应该是主键 - 而不是非唯一索引(实际上建议的索引应该 all 是主键)并且根据答案在其他地方,考虑到问题中定义的范围,主题和目录中的条目顺序是错误的。 @symcbean - 好点。不会对性能产生太大影响,但这显然是正确的做法。【参考方案3】:您的查询非常简单。我认为您的成本不会随着实施联接而降低。您可以尝试将索引放入适当的列
【讨论】:
【参考方案4】:在这里简单地选择更少的数据是显而易见的解决方案。
为什么每次运行查询时都需要知道每一列和每一行?解决这三个因素中的任何一个都会提高性能。
我想获取至少包含一个主题和类别的所有产品的列表
这意味着您不关心哪个主题和类别,在这种情况下.....
SELECT p.*
FROM themeitems ti, products p, catitems ci
WHERE p.ID = ti.PRODID
AND p.ID = ci.PRODID
有可能使查询运行速度显着加快 - 但您没有提供有关表结构、索引、数据量、引擎类型、查询缓存配置、数据更新频率的详细信息,查询运行的频率.....
更新
既然您已经提供了解释计划,那么很明显您只有非常少量的数据并且没有相关索引!!!!!!
您至少应该在 themeitems 和 catitems 表中的产品外键上添加索引。实际上,这些表的主键应该是产品 ID 和类别 ID/主题 ID,并且由于您可能拥有的产品多于类别或主题,因此这些字段应该在索引中按该顺序排列。 (即 PRODID、CATID 而不是 CATID、PRODID)
更新2
鉴于要求“获取包含至少一个主题和类别的所有产品的列表”,它可能会更快(但最大的胜利是减少连接数量并添加正确的索引)到...。
SELECT p.*
FROM product p
INNER JOIN (
SELECT DISTINCT ti.PRODID
FROM themeitems ti, catitems ci
WHERE ti.PRODID=ci.PRODID
) i ON p.id=i.PRODID
【讨论】:
“非常少量的数据” - 将所有行的值相乘,你会得到数亿 不——即使在非常快的硬件上,DBMS 也不会在 2.5 秒内处理“数亿”条记录(即便如此,数亿的笛卡尔积也不是我特别想描述的)大的)。即使没有索引,连接也会被优化为合并操作 “即使在非常快的硬件上,DBMS 也不会处理”——这是一个查询优化器预测(估计)。假设它为上限。我的观点是:我们无法从解释中得到实数,因为没有足够的数据。因此,您不能说“数据量非常小”,因为根据实际数据,返回的实际行数可能从 0 到 100s 不等。 “我有一个查询需要很长时间......目前大约需要 2.5 秒”。我指的不是计划。 "既然您已经提供了解释计划,那么很明显您获得的数据量非常少" --- “我指的不是计划" o_O 对不起,但从计划来看,它并不是显而易见,它不可能说返回的数据量。我什至计算了作为最大边界的确切行数 - 356,887,090,368【参考方案5】:我已经对此做出了回答,因为我无法将其作为评论
如果您想使用 JOINS 删除 FULL 表扫描,则基本的拇指关闭操作。 你应该先索引。
注意这并不总是适用于 ORDER BY/GROUP BY 与 JOINS 的组合,因为通常是 Using 临时的;需要使用文件排序。
额外因为这超出了问题的范围以及如何使用 ORDER BY/GROUP BY 结合 JOIN 来修复慢查询
因为 mysql 优化器认为它需要首先访问最小的表以获得最佳执行,这将导致 MySQL 不能总是使用索引对结果进行排序,需要使用临时表和文件排序来修复错误的排序顺序
(在此处阅读更多信息MySQL slow query using filesort 这是我解决此问题的方法,因为当 MySQL 需要基于磁盘的临时表时,使用临时表确实会降低性能)
【讨论】:
以上是关于细化这个 MySQL 查询?的主要内容,如果未能解决你的问题,请参考以下文章