代码之家  ›  专栏  ›  技术社区  ›  Gary

如何将多级别对象映射到indexeddb以获得最佳效率

  •  5
  • Gary  · 技术社区  · 8 年前

    我的问题是在indexeddb中列出一个数据结构。我开始构建一个小的网页功能,它逐渐发展成为一个更像Web学习工具的东西,现在更接近于一个独立的渐进式Web应用程序。使用localstorage已经很好地工作了,但是由于该工具的增长,对于某些用户来说,5MB限制可能会成为一个问题;因此,需要切换到indexedDB。

    该应用程序仅适用于台式机,允许用户构建模块组合并将数据以JSON字符串的形式保存到硬盘。当用户打开(上载)应用程序中的文件时,将解析字符串,并再次将整个项目组合写入LocalStorage,但在任何时候只有一个模块写入运行时对象。从按不同字段搜索数据和索引的角度来看,不需要“真正的”数据库,但只需要更大的存储量,因为如果组合中的每个模块都必须是单独的文件,那么对于用户来说,这将太令人困惑。

    保存到localstorage的大多数数据都来自三级对象,并且根据对象路径生成一个键来保存和检索数据。例如,object.level_1[key_1].level_2[key_2].level_3[key_3].height=10保存为localstorage.setitem('k1.k2.k3.h',10)。

    我的问题是,当移动到indexeddb时,哪一个更有效:一个与本地存储设置非常相似的单个对象存储,还是为组合的三个级别中的每一个单独的对象存储?

    如果可以将单个对象存储区视为类似于两列表,每个数据点有一行(键和值),则行计数将大于三个对象存储区的行计数之和,其中每一行是键和多个数据点的对象;但是,要更新三个对象存储区之一中的单个数据点,必须将数据库对象写入临时对象,更新数据点,然后再写入对象存储区。

    那么,问题是,哪一个更有效:在多行的单个表中搜索指向一个不那么复杂的值的单个唯一键,或者搜索具有较少行的三个表中的一个,但必须执行我认为等同于JSON解析、值更新和JSON的操作。字符串化以更新数据库中的相同值?

    虽然没有明确设置限制,但单个投资组合中预期的最大级别1对象数约为25,其中每个对象可能包含多达100个级别2对象,而每个级别3对象可能包含最多5个级别3对象。任何大于此的内容都很可能导致用户简单地构建单独的投资组合。

    因此,级别“1”对象存储大约是25行,级别“2”对象存储大约是2500行,级别“3”对象存储大约是12500行。每个级别的_1对象大约有40个数据点;每个级别的_2对象大约有100个数据点;每个级别的_3对象大约有20个数据点。因此,我认为单个ObjectStore的等效值为(25)(40)+(2500)(100)+(12500)(20)=501000行。

    我在从非常大的数据库中使用SQL提取数据方面有一定的经验,但我完全不知道如何设置数据库来按键定位数据。如果必须从上到下搜索,检查50.1万行中的每一行,直到找到匹配的键,那么对于三个对象存储来说,一个对象存储似乎是一个相当荒谬的选择。但是,如果indexeddb使用更有效的方法,那么一个objectstore可能更有效,这取决于更新三个objectstore之一的对象中的属性值的效率。

    我不是一个专业的程序员;所以,如果我的一些术语不准确,我很抱歉,我意识到我的问题是一个相当基础的层面;但我一直找不到任何信息来解决如何有效地“映射”一个对象到一个对象数据库。

    感谢您阅读我的问题以及您可以提供的任何指导。

    编辑/更新:

    谢谢你,乔希,花时间回答我的问题,并提供了一些需要考虑的项目。我还没有考虑到在应用程序的哪个阶段,不同类型的数据是如何写入浏览器存储的,这会影响对象存储数量的确定。

    在一个用户的会话中,通常只有两次大的数据移动:从硬盘上传一个JSON字符串,将其解析并写入浏览器存储,然后将浏览器存储读取到一个对象,将其串接并下载到硬盘。最有可能的是,用户希望这两个步骤至少需要足够的时间来要求某种形式的简短进度指示器。重要的时间项是存储数据编辑和创建新数据元素所需的时间。

    根据Josh的评论,也许,建立对象存储的一个好方法是考虑什么时候以及什么数据被屏幕写入浏览器存储,因为缺少更好的术语。在我的应用程序中,任何时候只有一个模块(项目组合中的一级对象)加载到运行时对象中。模块级数据有一个屏幕。退出该屏幕后,模块级数据中的任何更改都将写入存储器。

    模块中的每个Level_2对象都有自己的屏幕,当用户在Level_2对象屏幕之间导航时,屏幕输入元素中的内容将根据运行时对象的更改值进行检查,并将任何更改写入存储。

    在Level_2对象屏幕上,用户通过调用显示在Level_2屏幕顶部的窗口,将Level_3对象添加到特定的Level_2元素。当每个窗口关闭时,将执行类似的检查,并将任何数据更改写入存储器。

    创建与每个屏幕上显示和收集的数据对齐的对象存储似乎是有意义的,当然,还与对象级别对齐。然而,它仍然无法回答哪种数据结构最终将是最有效的,从而提供最佳的用户体验。

    除了一些关于数据库效率的经验法则之外,对于我的特定问题和情况,最好的方法可能是用两种方式对其进行编码,用比预期更多的最大模块和级别2和级别3对象填充投资组合,并测试写操作的性能。将数据读取到indexeddb。单对象存储的第一个方法应该很容易编码,因为它的设置与localstorage几乎完全相同。使用至少三个对象存储的第二种方法将花费更多时间,但对于我在这些领域背景有限的人来说,这可能是一种必要且值得学习的体验。

    如果我成功了,我将在不久的将来在这里分享结果。谢谢您。

    编辑:

    谢谢你的进一步解释。我不会以这种方式查询数据库,而是存储数据以便仅基于唯一键进行检索。但是,您之前关于在多个表中存储相同数据的评论最终在我的脑海中注册,我认为这大大简化了我的整个问题和方法。从本地存储的角度来看,我想的太多了。

    我认为可以很好地工作的是多个对象存储:一个对象存储,它为组合中的每个模块或级别1数据包含一个完整的对象,以及三个或四个对象存储,其中只包含“活动”或加载模块的数据子集。

    当用户选择要加载的模块时,它将在一个步骤中从模块对象存储区整体加载,并且该模块的子集(不同的对象级别)将写入许多不同的对象存储区。当用户在任何级别对模块数据进行编辑时,编辑将存储在适当的子集对象存储中,因为这将更快。

    如果用户正确退出/关闭模块,那么此时加载的对象将全部写入模块对象存储区,并且子集对象存储区将被清空。子集对象存储在那里 在用户无法正确退出或电源或操作系统出现故障时保留更改。

    打开应用程序时,将测试浏览器存储,以确定是否存在数据库,如果存在,则确定子集对象存储是否为空。如果为空,则执行模块的正确关闭和保存。如果不为空,则对模块的编辑不会以任何原因使其进入模块对象存储区,并且用户将提示我恢复或放弃保存在子集对象存储区中的编辑。如果用户选择恢复,那么必须将子集对象存储区中的数据收集到一个完整的模块中,并写入模块对象存储区。

    对于该应用程序中任何单个模块的预期最大大小,这应该可以正常工作;但是,如果模块的大小在整体加载时对浏览器来说太大,那么可以使用子集对象存储来填充屏幕;当用户退出模块时可以将这些子集收集在一起以构建一组完整的模块数据,并将其写入模块对象存储区,就像进行恢复一样。

    当然,如果由于模块过大而导致浏览器运行太慢,并且在运行时更改方法,则无法在运行时进行测试。我的意思是,如果在测试大型样本模块的过程中,发现浏览器运行太慢,那么需要实现第二种方法。

    我意识到我的特定问题不像回答中列出的项目那么有趣。然而,阅读这些一般概念有助于我更好地理解如何解决我对indexeddb不太感兴趣的用法,并避免将不必要的复杂性编码成简单的问题。再次感谢。

    1 回复  |  直到 8 年前
        1
  •  3
  •   Josh    8 年前

    我认为你有自己的答案,所以我的回答只是想推动你前进。

    NoSQL与传统的SQL数据库的主要区别在于 查询计划 . 查询计划是由SQL数据库提供的功能,在该数据库中,它接受查询,对其进行分析,然后将其转换为查找匹配记录并在结果集中返回给您的算法。查询规划涉及到选择最理想的方法,通常通过尽量减少涉及的步骤数、涉及的内存量或将要花费的时间。另一方面,您自己使用nosql。你必须成为一个通宵查询计划专家。

    这既是一种恩惠,也是一种负担。对于某些人来说,查询计划是一个复杂的悬崖,您很快就会发现自己在阅读一些令人困惑的东西。但是,如果您正在寻找一个更具技术性的答案,那么它将朝着这个方向发展,即更多地了解数据库如何进行查询规划。

    为了加快速度,我将应用与规范化和非规范化相同的常规知识。博伊斯密码子和正常形式1-5等等。NoSQL处于极端非规范化端。您存储的项目的“逻辑”结构是不相关的。使用NoSQL,您的目标不是一个好的传统和直观的模式。您的目标是高效地执行存储操作和查询。

    因此,要回答这个问题,您必须从对操作的简单分析开始。枚举应用程序执行的操作。哪些是最频繁的操作?你认为完成哪一项需要最长的时间?通过操作,这里我不讨论低级查询,也不讨论您的数据库在nosql/sql中的模式。这是一个抽象的层次。更抽象地思考。列举“为所有符合这些条件的人加载信息”、“删除那边的人”。我接受了你提到的一些问题,但我没有得到一个明确的列表,这个列表是正确答案中的重要标准。

    一旦你列举了这些操作,那么我认为你就更接近回答你的问题了。作为一个玩具例子,考虑一下更新。更新是否频繁?频繁的更新会表明一个对象存储是不好的,因为您必须加载大量不相关的东西,而只是为了更改一个对象的一个属性。考虑粒度。您需要一个对象的所有属性,还是只需要一些属性?想想什么是最频繁的操作?它是否根据某些标准加载对象列表?它是删除还是更新东西?想想什么东西是同时加载的(共定位)。当您加载2级对象的一个实例时,其他实例通常也加载吗?如果没有,那为什么要把它们放在一起呢?远离您的规范化模式,只需忘记它。您需要一个非规范化的模式,以某种方式存储数据,以便优化查询。最终的结果可能与你想象的完全不同。

    也许一个好的实验就是这个。伪代码执行实际重载提升的函数。您将直接遇到问题,并确定可能非常慢的功能部分。那么,您的问题的答案基本上就是什么数据结构会真正加速这些部分,或者至少比其他数据结构慢一些。

    编辑:一点跟进。NoSQL数据库和非规范化的一个相当违反直觉的特性是,最终可能会多次存储数据。有时在多个地方存储相同的数据是有意义的。因为它加快了查询速度。是的,它为不一致性引入了空间,并违反了SQL的无功能依赖性规则。但是,您可以通过使用多存储事务和一些注意事项来加强数据完整性(一致性)。更详细地说,您想要的存储可能只是您计划执行的查询的文字结果。对。为您计划执行的每个查询创建一个对象存储。在它们之间冗余存储数据。是的,听起来很疯狂和极端。这有点夸张。但是这种方法在使用NoSQL时很常见,并且得到了提升。

    编辑:这里有一个粗略的第一次尝试,只是头脑风暴一点,这是一个尝试,给你一个更具体的答案,根据猜测你实际上在做什么。

    您需要的是一个名为“设置”的对象存储。存储区中的每个对象都代表一个设置对象。单个设置对象具有设置ID、设置属性名称、设置属性值、级别1属性、级别2属性、级别3属性等属性。

    您的基本读取查询可能看起来像 SELECT * from Settings WHERE level1 = 'a' && level2 = 'b' .

    更进一步,您可以使用索引为某些视图进行优化。我们可以在一级属性上创建一个索引,在二级属性上创建一个索引,在一级+二级属性上创建一个索引。

    假设您最频繁的操作(需要最快的操作)是加载属于级别1、2和3的特定组合的所有设置。在所有3个上创建一个索引,然后只需遍历该索引即可。

    这个头脑风暴示例中的模式是一个单独的对象存储,以及一些索引来加速某些查询。考虑到索引基本上是派生的对象存储,您可以使用实际使用多个存储的概念参数,尽管实际上只使用一个。不管怎样,这可能会变得迂腐。这个例子的要点只是为了证明对象存储的模式与您如何概念化组合和级别的层次结构一点关系都没有。它只与快速执行所需的查询有关。