MySQL加入查询性能问题

时间:2011-03-11 08:33:42

标签: mysql sql optimization indexing

我正在运行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

当我运行explain时,除了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在providerid和providertype上有索引,而usertoprovider在userid和providerid上有索引

密钥的基数是: provider.id = 47,provider.type = 1,usertoprovider.userid = 1245,usertoprovider.providerid = 6

因此很明显没有使用索引。

此外,为了测试它,我继续前进:

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

查询执行时间从 8.1317秒更改为 0.0387秒。

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

有人可以解释为什么MySQL仍然运行完整扫描并且不使用密钥吗?可以采取哪些措施来避免它?

2 个答案:

答案 0 :(得分: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. 执行以下操作:
  3. provider.providertype='reg' OR EXISTS (SELECT * FROM usertoprovider WHERE userid=1 AND providerid = provider.ID)

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

答案 1 :(得分:0)

嗯,我知道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问题,而不是性能问题