0

我有一个简单的“事物”数据库,可以有零个或多个“类别”或“标签”。我编写了一个存储过程,它将获取给定类别中的前 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 个标签,性能很好——我最多可以在几十毫秒内得到前几百个匹配。

但是,如果我想知道总共有多少匹配项,则需要很长时间——至少要几秒钟。有比 just 更聪明的方法SELECT COUNT(id) FROM .... (rest of query above)吗?根据这个建议,该id字段已编入索引,但索引并没有太大帮助,因为它必须检查表中的每一行。tagsthings

我正在考虑实现分页,并且我知道LIMIT ?,?(or LIMIT ? OFFSET ?) 会很容易,但是最好向用户展示至少有多少总“匹配”期望的近似值。

4

5 回答 5

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

在 (dateamp,id) 的事物和 (id,tag) 的 thing_tags 上有一个索引。我在这里做了假设,每件事情的标签都是不同的。

于 2013-08-15T12:02:38.517 回答
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 支持内置(称为 counter_cache)。


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

也许你可以做这样的事情?这将计算thing_tags匹配@searchTag并具有thingbetween@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
于 2013-08-15T19:04:59.463 回答
1

从您的评论中,我会得出您有几个选择,各有利弊:

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

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

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

  4. 您通过在某处创建计数表来非规范化您的表。基本上这就是数据仓库所做的。这在更新模式下变得丑陋,但有效。我个人通常会尽量避免这种做法,因为当我说“丑陋”时,我的意思是丑陋。

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

  6. 结合这些选项,例如。与 3 和 5 一样,其中 5 支付了一些细节的图形指示器,而 3 为用户提供了一个采取行动的钩子。

  7. 问“这有意义吗”的问题。这可能会成为哲学问题,我不想激怒你的想法。然而,将 300 K 的项目组合在一起的标签真的有意义吗?您可以进行任何概念性的权衡吗?

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

于 2013-08-15T14:57:41.247 回答
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 操作很棘手。所以这可能是你的性能挂钩,它可能会促进语句分离。

优化的解决方案取决于您的设置 - 因此有测试空间。

于 2013-08-15T11:57:52.390 回答
0

如果它对遇到类似问题的任何人有帮助,我最终放弃了——我用更大(但仍然合理)的限制进行第二次查询,然后将结果呈现为“1-10 of 100+”(或任何更大的限制曾是)。这足以满足我的需求。

简短的回答是,在这种数据库中,没有很好的方法来获得这种查询的“非常接近”的估计值,而无需在其他地方手动维护单独的计数值。

于 2017-07-26T07:40:08.527 回答