我有一个简单的“东西”数据库,可以有零个或多个“类别”或“标签”。我编写了一个存储过程,它将获得给定类别中的前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 ?
)会让它变得简单,但向用户显示至少总数的近似值会很好“匹配“期待。
答案 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)
从你的评论我会画你有几个选项,都有利有弊:
广泛改进您的优化。这包括索引并将数据库的至少一半加载到RAM中。相信我300K行数可以非常快。然而,RAM花钱并调整成本时间。
不要向用户表示完整的“下一个1到926”,而是“NEXT”之类的内容。这很容易实现,因为您只需将限制增加一个但显示初始请求的行。如果您的数据库返回了您知道的+1结果,则必须代表NEXT
您可以从您请求限制300的数据库中扩展2而不是限制100,这样您就可以为用户提供+1 +2 +3 NEXT按钮
通过在某处创建计数表来对表进行非规范化。基本上这就是数据仓库所做的事情。这在更新模式下变得丑陋,但有效。我个人经常试图阻止这样的练习,因为当我说'丑'时我的意思是UGLY。
去寻求解释并接受这样一个事实:解释对孤独的果实没有帮助。这只是关于* 10 * 100 * 1000 * 10000 * 100000的想法。
结合这些选项,例如。 3和5,其中5支付了一些细节图形指标,3给了用户一个采取行动的钩子。
问'这是否有意义'的问题。这可能会变得具有哲学性,我不想激发你的想法。然而,标签真的有意义,将300 K的物品组合在一起吗?你可以去做任何概念上的交易吗?
考虑一下,如果稍微重新设计是您的选择。我从以前的对话中了解到,你在表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+”(或者其他更大的限制是)。这足以满足我的需求。
简短的回答是,在这种类型的数据库中,没有好的方法可以对这种查询进行“非常接近”的估计,而无需在其他地方手动维护单独的计数值。