【问题标题】:Estimating number of results from a MySQL "SELECT WHERE EXISTS" query?估计 MySQL“SELECT WHERE EXISTS”查询的结果数量?
【发布时间】:2013-08-15 11:46:06
【问题描述】:

我有一个简单的“事物”数据库,可以有零个或多个“类别”或“标签”。我编写了一个存储过程,它将获取给定类别中的前 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) 更聪明的方法吗?根据this suggestionid 字段已编入索引,但索引并没有多大帮助,因为它必须检查tags 表中things 中的每一行。

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

【问题讨论】:

  • 我想我只是计算了太多该死的结果。我希望“###### 的结果 1 - 20”能够与实际结果一样快地返回,但总数比我向用户展示的微小子集大几个数量级。我认为简单的性能调整/索引构建不会让我到达那里。我想我需要估计(也许有代表性的抽样?)或者将计数存储在其他地方。如果我说该表只获得不频繁的顺序插入,它会改变游戏规则吗?

标签: mysql innodb query-performance


【解决方案1】:

我认为以下应该算数

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 上有一个索引。 我在这里做了假设,每件事的标签都是不同的。

【讨论】:

  • 这确实给出了我想要的确切计数,它只需要比有限搜索长两到三个数量级:(
【解决方案2】:

哦,嗨,我在 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 并在 @start@end 之间有一个 thing

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】:

    从您的 cmets 中,我认为您有几个选择,各有利弊:

    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 表的连接。

    【讨论】:

      【解决方案4】:

      解释语句给出的计数指示并不准确,但非常快

      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 对字符串的操作很棘手。所以这可能是你的性能挂钩,它可能会促进语句分离。

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

      【讨论】:

      • 解释速度非常快,但实际上并没有给出最终结果的估计值。它显示了 thing_tags 的行数,但这是我查询的实际正确结果的 3 倍以上。
      • 我想,你需要努力解决你的问题。你能发布解释吗?
      • 我认为您的implode 不会成功;对于一些常见的标签值,查询将数以十万计。这就是问题的症结所在——我从 300k 条记录中选择第一条,比如说,100 条记录,但是计算 300k 条记录需要 10 秒,而实际结果会在 10 毫秒内返回……
      • 你有没有考虑过这样的事情:mysqlperformanceblog.com/2007/11/01/…? - “innodb_buffer_pool_size 70-80% 的内存是一个安全的赌注。我在 16GB 机器上将它设置为 12G。”我走这条路,通常不用担心计算 300K - 可能语句分离不是真的必要。
      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多