我正在运行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
因此很明显没有使用索引。
此外,为了测试它,我继续前进:
查询执行时间从 8.1317秒更改为 0.0387秒。
但是,具有providertype ='reg'的提供者值对所有用户都有效,我希望避免将这些值插入到usertoprovider表中,因为这些数据是多余的。
有人可以解释为什么MySQL仍然运行完整扫描并且不使用密钥吗?可以采取哪些措施来避免它?
答案 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'传递任何东西,但不能在你的多对多映射表中传递,那么最简单的方法可能是使用子查询:
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
)?
据我所知,有两种解决方案:
ORDER BY
子句,因为我认为这不会影响您的结果,但可能会严重降低您的查询速度。