【问题标题】:MySQL join query performance issueMySQL连接查询性能问题
【发布时间】:2011-03-11 08:33:42
【问题描述】:

我正在运行 be 查询

SELECT packages.id, packages.title, subcat.id, packages.weight
FROM packages ,provider, packagestosubcat, 
     packagestocity, subcat, usertosubcat, 
     usertocity, usertoprovider 
WHERE packages.endDate >'2011-03-11 06:00:00' AND 
      usertosubcat.userid = 1 AND 
      usertocity.userid = 1 AND 
      packages.providerid = provider.id AND 
      packages.id = packagestosubcat.packageid AND 
      packages.id = packagestocity.packageid AND 
      packagestosubcat.subcatid = subcat.id AND 
      usertosubcat.subcatid = packagestosubcat.subcatid AND 
      usertocity.cityid = packagestocity.cityid AND 
      (
          provider.providertype = 'reg' OR 
          (
              usertoprovider.userid = 1 AND 
              provider.providertype != 'reg' AND 
              usertoprovider.providerid = provider.ID
          )
      ) 
GROUP BY packages.title 
ORDER BY subcat.id, packages.weight DESC

当我运行解释时,除了对 usertoprovider 表的扫描(似乎没有使用表的键)之外,一切似乎都正常:

id select_type table            type    possible_keys         key       key_len ref                       rows Extra
1  SIMPLE      usertocity       ref     user,city             user      4       const                     4    Using temporary; Using filesort
1  SIMPLE      packagestocity   ref     city,packageid        city      4       usertocity.cityid         419  
1  SIMPLE      packages         eq_ref  PRIMARY,enddate       PRIMARY   4       packagestocity.packageid  1    Using where
1  SIMPLE      provider         eq_ref  PRIMARY,providertype  PRIMARY   4       packages.providerid       1    Using where
1  SIMPLE      packagestosubcat ref     subcatid,packageid    packageid 4       packages.id               1    Using where
1  SIMPLE      subcat           eq_ref  PRIMARY               PRIMARY   4       packagestosubcat.subcatid 1  
1  SIMPLE      usertosubcat     ref     userid,subcatid       subcatid  4       const                     12   Using where
1  SIMPLE      usertoprovider   ALL     userid,providerid     NULL      NULL    NULL                      3735 Using where

正如您在上面的查询中看到的,条件本身是:

provider.providertype = 'reg' OR 
(
    usertoprovider.userid = 1 AND 
    provider.providertype != 'reg' AND 
    usertoprovider.providerid = provider.ID
)

provider 和 usertoprovider 这两个表都已编入索引。 provider 在 providerrid 和 providertype 上有索引,而 usertoprovider 在 userid 和 providerid 上有索引

键的基数是: provider.id=47,provider.type=1,usertopprovider.userid=1245,usertopprovider.providerid=6

所以很明显没有使用索引。

更进一步,为了测试它,我继续:

  • 复制了 usertoprovider 表
  • 将所有 providertype='reg' 的提供者值插入到克隆表中
  • 将条件简化为 (usertoprovider.userid = 1 AND usertoprovider.providerid = provider.ID)

查询执行时间从8.1317秒变为0.0387秒

不过,providertype='reg' 的提供者值对所有用户都有效,我想避免将这些值插入到所有用户的 usertoprovider 表中,因为这些数据是多余的。

谁能解释一下为什么 MySQL 仍然运行完整扫描并且不使用密钥?有什么办法可以避免呢?

【问题讨论】:

  • 您能否给出 CREATE TABLE 语句并将您的查询包装在多行中?您可能需要检查索引提示:dev.mysql.com/doc/refman/5.5/en/index-hints.html
  • 对不起,我已经整理了资料。
  • 嗯...对于这些类型的连接,我真的建议您使用 ANSI-JOIN 语法。只是让事情更具可读性,您可以轻松地将实际谓词与 JOIN 条件分开......

标签: mysql sql optimization indexing


【解决方案1】:

似乎provider.providertype != 'reg' 是多余的(始终为真),除非 provider.providertype 可以为空并且您希望查询在 NULL 时失败。

不应该!=<> 而不是标准SQL,尽管MySQL 可能允许!=

关于表扫描的成本

全表扫描不一定比遍历索引更昂贵,因为遍历索引仍然需要多次页面访问。在许多数据库引擎中,如果您的表足够小以容纳几页,并且行数足够少,则进行表扫描会更便宜。数据库引擎根据表的数据和索引统计信息做出此类决策。

这个案例

但是,在您的情况下,也可能是因为您的 OR 子句中的另一条腿:provider.providertype = 'reg'。如果 providertype 是“reg”,那么这个查询会加入 usertoprovider 的所有行(很可能不是你想要的),因为它是一个多表交叉连接。

数据库引擎在确定您可能需要 usertoprovider 中的所有表行时是正确的(除非提供者类型中没有一个是“reg”,但引擎也可能知道!)。

查询隐藏了这一事实,因为您稍后将在 (MASSIVE!) 结果集上进行分组,并且只返回包 ID,因此您不会看到返回了多少 usertoprovider 行。但它会运行得很慢。去掉 GROUP BY 子句,看看你实际上是在强制数据库引擎处理多少行!!!

如果您填写 usertoprovider 表,您会看到速度大幅提升的原因是因为每一行都参与了一个连接,并且在“reg”的情况下没有发生完全交叉连接。之前,如果您在 usertoprovider 中有 1,000 行,则 type="reg" 的每一行都会将结果集扩展 1,000 次。现在,该行只与 usertoprovider 中的一行连接,结果集没有展开。

如果你真的想通过 providertype='reg' 传递任何东西,但不是在你的多对多映射表中,那么最简单的方法可能是使用子查询:

  1. 从 FROM 子句中删除 usertoprovider
  2. 执行以下操作:

provider.providertype='reg' OR EXISTS (SELECT * FROM usertoprovider WHERE userid=1 AND providerid = provider.ID)

另一种方法是在 usertoprovider 上使用 OUTER JOIN —— 任何不在表中的带有“reg”的行将返回 one NULL 行,而不是扩展结果集。

【讨论】:

  • 较新的 MySQL 版本允许 '' 和 '!='
  • 好的。 :-) 不知道,但坚持标准 SQL 术语通常是个好主意。
  • provider 表中的条目有两个不同的 providertype 值,而不是一个值。当我复制 usertoprovider 表并将提供程序表中具有 providertype='reg' 的所有条目添加到 usertoprovider 表中时。将查询更改为 (usertopprovider.userid = 1 AND usertoprovider.providerid = provider.ID),查询时间从 8.1317 秒下降到 0.0387 秒。尽管如此,由于 providertype='reg' 的提供者条目对所有用户都有效,我想避免冗余数据,而不是将它们插入到所有用户的 usertoprovider 表中。
  • 然后您需要修改您的查询。就目前而言,如果它是“reg”,它会交叉加入 整个 usertoprovider 表。比如说,如果您有 20 行带有“reg”,并且在 usertoprovider 中有 100 行,那么您的结果集将扩展 2,000 倍。这种意外的交叉连接是您的查询像狗一样运行的原因。请参阅我的编辑以进行澄清。
  • 感谢 Stephen,从 FROM 子句中删除 usertoprivder 并优化条件解决了问题。
【解决方案2】:

嗯,我知道 MySQL 在分组方面做了一些有趣的事情。在任何其他 RDBMS 中,您的查询甚至不会被执行。这到底是什么意思,

SELECT packages.id 
[...]
GROUP BY packages.title 
ORDER BY subcat.id, packages.weight DESC

您想按title 分组。然后在标准 SQL 语法中,这意味着您只能选择 title 和其他列的聚合函数。 MySQL 神奇地尝试执行(并且可能猜测)您可能打算执行的内容。那么 期望被选为 packages.id 的是什么?每个 title 的第一个匹配包 ID?还是最后一个? ORDER BY 子句对于分组意味着什么?如何按不属于结果集的列进行排序(因为只有 packages.title 真的是)?

据我所知,有两种解决方案:

  1. 您的查询走在正确的轨道上,然后删除 ORDER BY 子句,因为我认为它不会影响您的结果,但它可能会严重减慢您的查询速度。
  2. 您遇到的是 SQL 问题,而不是性能问题

【讨论】:

  • 卢卡斯,那是我的错。我想简化帖子中的查询,所以我从 select 中删除了所有没有任何意义的值。我错误地没有注意到查询实际上不会执行。我现在要修复帖子。
  • 这对我来说仍然没有意义,但也许这不是性能问题的原因......你为什么不按packages.id, packages.title, subcat.id, packages.weight分组?
  • 有一个业务限制迫使我将多行插入到 packages 表中。这导致一些用户在他们的结果中获得相同的包,因为相同的包将在包表中出现两次,具有两个不同的 id。由于这个限制,我不能强制标题字段是唯一的,并且按标题分组强制用户只能获得一次包。
猜你喜欢
  • 2011-12-15
  • 2013-03-27
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 2012-10-29
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
相关资源
最近更新 更多