【问题标题】:Optimal DB query for prefix search前缀搜索的最佳数据库查询
【发布时间】:2012-06-15 15:16:13
【问题描述】:

我有一个数据集,它是一个前缀范围列表,并且前缀的大小不尽相同。以下是几个例子:

low: 54661601   high: 54661679   "bin": a
low: 526219100  high: 526219199  "bin": b
low: 4305870404 high: 4305870404 "bin": c

我想查找哪个“bin”对应于具有相应前缀的特定值。例如,值5466160179125211 将对应于“bin”a。在重叠(很少)的情况下,我们可以返回最长的前缀或所有的前缀。

最佳算法显然是某种可以插入 bin 对象的树,其中树的每个连续级别代表越来越多的前缀。

问题是:我们如何在数据库中实现这一点(在一个查询中)?允许更改/添加到数据集。什么是最好的数据和查询设计?最好使用 mongo 或 MySQL 来回答。

【问题讨论】:

    标签: mysql algorithm mongodb tree prefix


    【解决方案1】:

    如果您对前缀范围内的重叠数量做出一个温和的假设,则可以使用 MongoDB 或 MySQL 以最佳方式执行您想要的操作。在下面的答案中,我将使用 MongoDB 进行说明,但是将这个答案移植到 MySQL 应该很容易。

    首先,让我们重新表述一下这个问题。当您谈论匹配“前缀范围”时,我相信您实际上在谈论的是在 lexicographic 排序下找到正确的范围(直观地说,这只是字符串的自然字母顺序)。例如,前缀匹配 54661601 到 54661679 的数字集恰好是当写为字符串时,在字典上大于或等于“54661601”但在字典上小于“54661680”的数字集。所以你应该做的第一件事是在你的所有 high 边界上加 1,这样你就可以用这种方式表达你的查询。在 mongo 中,您的文档看起来像

    {low: "54661601", high: "54661680", bin: "a"}
    {low: "526219100",  high: "526219200",  bin: "b"}
    {low: "4305870404", high: "4305870405", bin: "c"}
    

    现在问题变成了:给定一组 [low, high) 形式的一维区间,我们如何快速找到哪些区间包含给定点?最简单的方法是在 lowhigh 字段上使用索引。让我们使用 high 字段。在 mongo 外壳中:

    db.coll.ensureIndex({high : 1})
    

    现在,让我们假设间隔根本不重叠。如果是这种情况,那么对于给定的查询点“x”,包含“x”的唯一可能区间是具有大于“x”的最小 high 值的区间。因此我们可以查询该文档并检查其 low 值是否也小于“x”。例如,这将打印出匹配间隔,如果有的话:

    db.coll.find({high : {'$gt' : "5466160179125211"}}).sort({high : 1}).limit(1).forEach(
           function(doc){ if (doc.low <= "5466160179125211") printjson(doc) } 
      )
    

    现在假设不是假设区间根本不重叠,而是假设每个区间与小于 k 个相邻区间重叠(我不知道 k 的值是多少 会为你做到这一点,但希望它是一个小的)。在这种情况下,您可以在上面的“限制”中将 1 替换为 k,即

     db.coll.find({high : {'$gt' : "5466160179125211"}}).sort({high : 1}).limit(k).forEach(
           function(doc){ if (doc.low <= "5466160179125211") printjson(doc) } 
      )
    

    这个算法的运行时间是多少?索引是使用 B-trees 存储的,因此如果您的数据集中有 n 个间隔,则需要 O(log n) 时间来查找第一个匹配的文档 high 值,然后 O(k) 时间迭代下一个 k 个文档,总共 O(log n em> + k) 时间。如果 k 是常数,或者实际上小于 O(log n),那么这是渐近最优的(这是在标准计算模型中;我不是计算外部存储器传输的数量或任何花哨的东西)。

    只有当 k 很大时才会出现这种情况,例如,如果某个大区间几乎包含所有其他区间。在这种情况下,运行时间为 O(n)。如果您的数据是这样的结构,那么您可能需要使用不同的方法。一种方法是使用 mongo 的“2d”索引,您的 lowhigh 值编码 xy 坐标.然后,您的查询将对应于查询 x - y 平面的给定区域中的点。这在实践中可能会做得很好,尽管使用当前的 2d 索引实现,最坏的情况仍然是 O(n)。

    有许多理论结果对于 k 的所有值都实现了 O(log n) 性能。它们的名称包括优先级搜索树、段树、区间树等。但是,这些是您必须自己实现的专用数据结构。据我所知,目前没有流行的数据库实现它们。

    【讨论】:

      【解决方案2】:

      “最佳”对不同的人可能意味着不同的东西。似乎你可以做一些事情,比如将你的低值和高值保存为 varchars。那么你所要做的就是

      select bin from datatable where '5466160179125211' between low and high
      

      或者,如果您出于某种原因将值作为整数保留在表中,则可以在查询中进行 CAST。

      我不知道这是否会给大型数据集带来糟糕的性能。我希望我能理解你想要做什么。

      【讨论】:

        【解决方案3】:

        使用 MySQL,您可能必须使用存储过程,调用该存储过程将值映射到 bin。所述过程将查询每一行的桶列表并进行算术或字符串操作以找到匹配的桶。您可以通过使用固定长度的前缀来改进此设计,这些前缀排列在固定数量的层中。你可以为你的树分配一个固定的深度,每层都有一个表格。使用这两种方法都不会获得类似树的性能。

        如果你想做一些更复杂的事情,我怀疑你必须使用不同的平台。

        Sql Server 有一个 Hierarchy 数据类型: http://technet.microsoft.com/en-us/library/bb677173.aspx

        PostgreSQL 有一个 cidr 数据类型。我不熟悉它的查询支持级别,但理论上你可以在你的数据库中构建一个路由表并使用它来分配存储桶: http://www.postgresql.org/docs/7.4/static/datatype-net-types.html#DATATYPE-CIDR

        【讨论】:

          【解决方案4】:

          佩顿! :)

          如果您需要将所有内容保存为整数,并希望它与单个查询一起使用,这应该可以:

          select bin from datatable where 5466160179125211 between 
                low*pow(10, floor(log10(5466160179125211))-floor(log10(low))) 
             and ((high+1)*pow(10, floor(log10(5466160179125211))-floor(log10(high)))-1);
          

          在这种情况下,它将在数字 5466160100000000(具有低前缀的最小数字和与要查找的数字相同的位数)和 546616799999999(具有高前缀的最大数字和相同的位数)之间进行搜索作为要查找的数字)。在高前缀的位数多于低前缀的情况下,这仍然有效。它也应该在数字短于前缀长度的情况下工作(我认为),在这种情况下,先前解决方案中的 varchar 代码可能会给出不正确的结果。

          您需要进行试验,以比较在查询中使用大量内联数学的性能(如在此解决方案中)与使用 varchars 的性能。

          编辑:即使在没有索引的大表上,性能似乎也非常好;如果您可以使用 varchars,那么您可以通过索引低列和高列来进一步提高性能。请注意,如果任何前缀具有初始零,您肯定希望使用 varchars。这是一个修复,以允许在使用 varchars 时数字比前缀短的情况:

          select * from datatable2 where '5466' between low and high
              and length('5466') >= length(high);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-05-18
            • 1970-01-01
            • 1970-01-01
            • 2017-08-06
            • 2020-09-22
            • 1970-01-01
            • 1970-01-01
            • 2020-04-09
            相关资源
            最近更新 更多