【问题标题】:Recursive data processing performance using Java and SQLite使用 Java 和 SQLite 的递归数据处理性能
【发布时间】:2010-10-17 12:59:39
【问题描述】:

如果您的答案与 Java / SQLite 无关,我很乐意阅读。

环境

我使用以下方案将项目存储在数据库中:

###################
#       Item      #    
###################
#      _id        #    This is the primary key
#    parent_id    #    If set, it the ID of the item containing this item
#      date       #    An ordinary date
#  geocontext_id  #    Foreign key to a pair of named coordinates
###################

###################
#   Geocontext    #    
###################
#       _id       #    This is the primary key
#       name      #    Way for the user to label a pair of coordinates (e.g : "home", "work")
#         x       #    One of the coordinate
#         y       #    The other one
###################

问题

我必须根据地理上下文和日期过滤项目。如果项目都在同一级别,这将是一件容易的事,但诀窍是它是递归的。例如:

root
      |_item 1
      |_item 2 
      |      |_item 4
      |      |_item 5
      |             |_item 6
      |_item 3
      |      |_item 8
      |             |_item 10
      |_item 11
      |       |_item 12
      |_item 7

递归深度没有明确的限制。

现在,如果我们在任何节点中并使用日期“4 月 1 日”进行过滤,我们不仅必须看到与日期匹配的节点中直接包含的项目,而且 我们必须看到包含与日期匹配的项目

E.G : 我们在“Items 2”中,如果“item 6”匹配日期,那么我们认为“item 5”也匹配日期,我们必须保留它。如果我们在根目录,则必须显示第 2 项。

地理上下文也是如此,但更难,因为:

  • 它存储在另一个表中。
  • 匹配上下文是一项昂贵的数学计算。

当然,强制匹配会导致软件运行缓慢并且用户体验非常差。

注意:我不需要显示树。我从树中显示过滤数据的列表。我们必须只看到顶级元素的平面列表。挑战在于根据所有子层级决定是否显示每个元素。

我是如何解决的

我想我可以通过使用更多的表来缓存平面数据来缓解这个问题:

###################
# Geocontex_cache #    
###################
#     item_id     #     I can Join the items table on this field
#     child_id    #     I can delete / update a child, and so delete / update the cache
#  geocontext_id  #     I can delete / update a geocontext, and so delete / update the cache
#        x        #      Here, I can brute force :-)
#        y        # 
###################

###################
#    Date_cache   #    
###################
#     item_id     #     
#     child_id    #    
#       date      #    
###################

这似乎是合理的,但我还没有尝试过。不过,它应该有以下缺点:

  • 我将昂贵的过程转移到了 /set/create/delete 方法 将不得不管理缓存的日期。 这将是一个麻烦的代码 编写和维护。五深 级别项目将触发一个过程 将递归地命中五个父母。

  • 数据库的大小可以 变得巨大。五层深度 项目存储缓存数据为五个 父母。不知道有没有关系 因为这是一个单用户应用程序 手动输入。我不认为任何人 将插入超过 1000 个项目 深度超过 10 级。

现在好消息是我们从 金字塔的底部到顶部,而不是 另一种方式,所以它没有 看起来很可怕。我什么时候 必须处理父项 删除,这将是另一个不错的 头痛,但我把它留到另一个 问题;-)。

现在我的问题

您将如何以最佳方式存储数据和处理过滤?

可选:

我应该定义一个明确的递归深度限制吗? 我应该使用 SQL 还是 Java 执行过滤? SQL 肯定会更快,但在 Java 中匹配地理上下文要容易得多。

当我在 Android 平台上工作时,我有以下限制:

  • Java 是唯一可用的语言, 而不是整个标准库。

  • SQLite 是唯一可用的 DBMS。

  • 性能和内存很重要 问题。如果你不得不选择, 电池寿命,因此 性能是重中之重。

  • Exotics 外部库可能无法使用 可以使用。

P.S:我深入研究了 SO,发现了一些有趣的信息(尤其是 What is the most efficient/elegant way to parse a flat table into a tree?)。这是一个提示,但不是问题解决者。

【问题讨论】:

  • 澄清一下,你显示所有匹配的孩子,对吗?因此,如果您从第 2 项和第 6 项匹配查看,您将在列表中显示第 5 项和第 6 项,对吗?还是只显示第 5 项作为最匹配的子项?
  • 另一个问题,地理上下文是共享的,还是有其他原因将它们放在单独的表中?
  • 我只会将第 5 项显示为最接近的匹配项。地理上下文不共享,但可以单独管理,因此用户可以在地理文本目录中进行选择。
  • 顺便说一句,我不应该把我自己的解决方案排除在问题之外并将其放在答案中吗?

标签: java android sqlite recursion


【解决方案1】:

1) 首先,让我们看一下简单地将所有内容放入内存中。这是简单、灵活、最重要的是快速的解决方案。缺点包括您必须在启动时将所有内容读入内存(给用户一个漂亮的加载栏,他们甚至不会注意到),并且可能需要做一些额外的工作以确保所有内容都反映到磁盘用户认为是,因此数据不会丢失。

在这个分析中,我对 Android/Dalvik 做了一些一般性的假设,我不太了解,所以希望它有点准确 :) 请记住 G1 有 192MB 的 RAM。此外,您上面的假设是最多大约 1000 个项目。

Object superclass ~ 8 bytes
parent/child pointer ~ 4 bytes
date (long) ~ 8 bytes
name (non interned string avg 32 chars) ~ 64 bytes
x point (int) ~ 4 bytes
y point (int) ~ 4 bytes

Total = 92 bytes + possible memory alignment + fudge factor = 128 bytes
1000 items = 125kB
10000 items = 1.22MB

注意:我意识到一个孩子只能有一个指针,而父母可以有多个孩子。但是,parent->child 指针的数量是 (elements - 1),因此 parent->child 指针的平均成本是 (elements - 1)/elements ~ 1 个元素或 4 个字节。这假定了一个不分配未使用内存的子结构,例如 LinkedList(与 ArrayList 相对)

2) 我的书呆子说,这将是一个有趣的地方来分析 B+ 树,但我认为这对于你目前想要的东西来说太过分了 :) 然而,无论你结束什么解决方案采用后,如果您没有将所有内容都保存在内存中,那么您肯定会希望尽可能多地在内存中缓存树的顶层。这可能会大大减少磁盘活动量。

3) 如果您不想占用所有内存,另一种可能的解决方案可能如下。 Bill Karwin 建议使用相当优雅的RDBMS structure called a Closure Table 来优化基于树的读取,同时使写入更加复杂。将此与顶级缓存相结合可能会给您带来性能优势,尽管我会在相信之前对此进行测试:

评估视图时,请使用您记忆中的任何内容来评估尽可能多的孩子。对于那些不匹配的子节点,使用带有适当 where 子句的闭包表和平面表之间的 SQL 连接来确定是否有任何匹配的子节点。如果是这样,您将在结果列表中显示该节点。

希望这一切都有意义,并且似乎可以满足您的需求。

【讨论】:

  • 有时我对他在 SO 上的答案质量感到非常惊讶。
【解决方案2】:

这可能是题外话,但是..您是否考虑过使用序列化?

Google Protocol Buffers 可用于以非常有效的方式(时间和空间)序列化数据,然后您必须创建合适的树结构(查看任何 CS 书籍)来帮助搜索。

我提到协议缓冲区是因为作为 Google 库,它们可能在 Android 上可用。

只是一个想法。

【讨论】:

  • +1 只是为了让我思考。但如果你能给我更多的细节,我会很高兴。当它来自序列化时,我是一个完全的菜鸟。由于我的网络背景和尝试在 POST / GET 方法中传递状态对象时丢失状态对象的麻烦,我总是试图避免它。
  • code.google.com/apis/protocolbuffers 只需查看任何标准 Java 文本即可了解序列化的详细信息。我们在大型生产系统中使用它来存储归档数据,它非常快速且易于使用。
【解决方案3】:

我听了 Soonil 的声音并尝试了 «闭表»。我添加了下表:

################
#   Closure    #
################
# ancestor_id  #
#   item_id    #
################

如果您像我一样从未使用过该模型,那么它就是这样工作的:

您为层次结构中的每个直接或间接关系添加一行。如果 C 是 B 的孩子,B 是 A 的孩子,那么你有:

ancestor    item
   B         C
   A         B
   A         C      # you add the indirect relationship   
   A         A
   B         B
   C         C      # don't forget any item is in relation with himself 

但是,使用此方案,您缺少一个重要信息:直接关系是什么?如果您只想要一个项目的直接子级怎么办?

为此,您可以在闭包表中添加带有 bool 的列 is_direct,或者您可以将列 parent_id 保留在 item 表中。我这样做是因为它阻止了我重写很多以前的代码。

好消息是我现在可以在一个查询中检查项目是否与日期或地理上下文匹配。

例如,如果我正在浏览项目编号 4 中包含的所有项目,并且只想获取匹配或包含与日期 D 匹配的子项的项目:

SELECT ti.parent_id, ti.id, ti.title 
FROM item AS di                                  # item to filter with the date
              JOIN closure AS c                  # closure table
                  ON (di.id = c.item_id) 
              JOIN item AS ti 
                  ON (c.ancestor_id = ti.id)     # top item to display
WHERE di.date = D                                # here you filter by date   
AND ti.parent_id = 4                             # here you ensure you got only the top items

所以我可以扔掉我所有的*_cache 表。我还有很多工作要做UPDATE / DELETE / CREATE,但一切都是集中的,而且大部分都是程序性的,而不是递归的。很酷。

唯一的痛苦是我必须递归地将一个项目添加到它的所有祖先。但是得到祖先是一问一答的,所以真的很合理。当然,闭包表占用了很多空间,但就我而言,我不在乎。如果您正在寻找性能,请不要忘记对其进行索引...

喜欢这个 SQL 技巧,非常感谢大家!乍一看有点棘手,但一旦完成就很明显了。

【讨论】:

    【解决方案4】:

    AFAICT,您可以在 SQLite 中使用分层查询(谷歌搜索“CONNECT BY”“START WITH”)...

    【讨论】:

    • 你确定吗?我真的认为这是一个 Oracle noly 关键字。我尝试了一个快速的谷歌,但一无所获......
    猜你喜欢
    • 1970-01-01
    • 2017-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-03
    • 1970-01-01
    • 2015-02-20
    • 1970-01-01
    相关资源
    最近更新 更多