估计MySQL“SELECT WHERE EXISTS”查询的结果数量?

时间:2013-08-15 11:46:06

标签: mysql innodb query-performance

我有一个简单的“东西”数据库,可以有零个或多个“类别”或“标签”。我编写了一个存储过程,它将获得给定类别中的前N个对象,并且性能非常好。它看起来像

SELECT * FROM things
WHERE things.datestamp > @start AND things.datestamp < @end
  AND EXISTS (
    SELECT 1 from thing_tags
    WHERE things.id = thing_tags.thing_id
      AND thing_tags.tag = @searchTag
  )
LIMIT ?

有几十万个“东西”,每个都有大约0-5个标签,性能很好 - 我最多可以在几十毫秒内获得前几百个匹配。

但是,如果我想知道总共有多少匹配,那么至少需要几年 - 几秒钟。有没有比SELECT COUNT(id) FROM .... (rest of query above)更聪明的方法? id字段按this suggestion编制索引,但索引没有太大帮助,因为它必须检查tags中每行的things表。

我正在考虑实施分页,我知道LIMIT ?,?(或LIMIT ? OFFSET ?)会让它变得简单,但向用户显示至少总数的近似值会很好“匹配“期待。

5 个答案:

答案 0 :(得分:2)

我认为以下应该给出一个计数

SELECT count(id) FROM things, things_tags
WHERE things.datestamp > @start AND things.datestamp < @end
  AND things.id=thing_tags.thing_id
  AND things_tags.tag = @searchTag
  GROUP BY things.id

使用(datestamp,id)的东西的索引和(id,tag)的thing_tags。 我在这里假设每个事物的标签都是不同的。

答案 1 :(得分:1)

从你的评论我会画你有几个选项,都有利有弊:

  1. 广泛改进您的优化。这包括索引并将数据库的至少一半加载到RAM中。相信我300K行数可以非常快。然而,RAM花钱并调整成本时间。

  2. 不要向用户表示完整的“下一个1到926”,而是“NEXT”之类的内容。这很容易实现,因为您只需将限制增加一个但显示初始请求的行。如果您的数据库返回了您知道的+1结果,则必须代表NEXT

  3. 您可以从您请求限制300的数据库中扩展2而不是限制100,这样您就可以为用户提供+1 +2 +3 NEXT按钮

  4. 通过在某处创建计数表来对表进行非规范化。基本上这就是数据仓库所做的事情。这在更新模式下变得丑陋,但有效。我个人经常试图阻止这样的练习,因为当我说'丑'时我的意思是UGLY。

  5. 去寻求解释并接受这样一个事实:解释对孤独的果实没有帮助。这只是关于* 10 * 100 * 1000 * 10000 * 100000的想法。

  6. 结合这些选项,例如。 3和5,其中5支付了一些细节图形指标,3给了用户一个采取行动的钩子。

  7. 问'这是否有意义'的问题。这可能会变得具有哲学性,我不想激发你的想法。然而,标签真的有意义,将300 K的物品组合在一起吗?你可以去做任何概念上的交易吗?

  8. 考虑一下,如果稍​​微重新设计是您的选择。我从以前的对话中了解到,你在表thing_tags中为同一个东西存储了多个(甚至300K +)相同标签字符串的行。这意味着你有一个非规范化的字符串篮子,它可以射击你的索引或你的索引内存利用率,这都会降低你的性能。将标记字符串放在标记表中,然后使用“bridge”/ n:n table tag2thing,其中包含唯一的字段:tagid和thingid。完成后,拆分语句是有意义的:1。搜索标签的ID,然后2.依靠tag2things和你的东西表的连接。

答案 2 :(得分:1)

哦嗨,我在Cloudspace工作(我们写了你链接到的博客文章)。

一种方法是更改​​things表并添加tags_count列。然后,无论您在何处创建或销毁thing_tags,都可以添加更新查询以增加或减少相应的thing

这将允许您选择类似

的计数
SELECT SUM(tags_count)
FROM things
WHERE things.datestamp > @start AND things.datestamp < @end

应该更快,更准确。

我不确定你使用的语言/框架是什么,但是由于你使用Ruby on Rails的可能性很小,Rails支持这个built in(称为counter_cache)。


编辑:我刚刚意识到你也受到@searchTag的限制,所以我不确定在这种情况下我的建议会有多大帮助。

也许你可以做这样的事情?这会计算thing_tags匹配@searchTag并在thing@start之间设置@end

SELECT count(thing_tags.id)
FROM thing_tags
  INNER JOIN things
    ON thing_tags.thing_id = things.id
WHERE things.datestamp > @start
  AND things.datestamp < @end
  AND thing_tags.tag = @searchTag

答案 3 :(得分:0)

解释陈述给出了计数的指示,这些计数不准确,但非常快

http://dev.mysql.com/doc/refman/5.0/en/explain.html

所以尝试这样的事情:

explain SELECT * FROM things,thing_tags
WHERE things.datestamp > @start AND things.datestamp < @end
  AND   things.id = thing_tags.thing_id AND thing_tags.tag = @searchTag

另一个更新: 这项工作有点你有一个索引id,关于事物的日期戳和things.tag上的索引标签

如果你分开查询可以实现强大的优化(伪代码php + mysql) 成:

1. thingids=implode(',',Select thing_id from thing_tags where thing_tags.tag = @searchTag)
2a. explain SELECT * FROM things WHERE things.datestamp > @start AND things.datestamp < @end
      AND   things.id in (@thingids)

2b. SELECT count(*) FROM things WHERE things.datestamp > @start AND things.datestamp < @end
      AND   things.id in (@thingids)

2a和2b可以交替运行。

通常对字符串的innodb操作很棘手。所以这可能是你的性能钩子,这可能促进语句分离。

优化的解决方案取决于您的设置 - 因此可以进行测试。

答案 4 :(得分:0)

如果它有助于任何有类似问题的人,我最终放弃了 - 我做了第二个查询,但是更大(但仍然合理)的限制,然后将结果渲染为“1-10 of 100+”(或者其他更大的限制是)。这足以满足我的需求。

简短的回答是,在这种类型的数据库中,没有好的方法可以对这种查询进行“非常接近”的估计,而无需在其他地方手动维护单独的计数值。