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

时间:2018-05-27 03:37:16

标签: indexeddb

我的问题是在indexedDB中布局数据结构。我开始构建一个小型网页功能,这个功能已经发展成为一种网络学习工具,现在更接近于独立的渐进式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时,这更有效:单个objectStore很像localStorage设置,或者是三个组合中每个级别的单独objectStore?

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

那么问题是,哪个更有效:在一个包含许多行的表中搜索指向一个不太复杂的值的单个唯一键,或者在三个表中搜索一个行数较少但必须执行的操作我认为相当于JSON解析,值更新和JSON stringify来更新数据库中的相同值?

虽然未明确设置限制,但单个投资组合中预期的最大level_1对象数约为25,其中每个对象最多可包含100个level_2对象,而每个对象最多可包含大约5个level_3对象。任何大于此的东西都很可能会导致用户简单地构建单独的投资组合。

因此,level_1 objectStore约为25行,level_2 objectStore约为2500行,level_3 objectStore约为12,500行。每个level_1对象有大约40个数据点;每个level_2对象有大约100个数据点;每个level_3对象有大约20个数据点。所以,我认为单个objectStore将具有相当于(25)(40)+(2500)(100)+(12,500)(20)= 501,000行。

我在从非常大的数据库中使用SQL提取数据方面经验不足,但对于如何设置数据库按键定位数据一无所知。如果必须从上到下搜索501,000行中的每一行,直到找到匹配的键,那么一个objectStore对于三个objectStore而言似乎是一个荒谬的选择。但是,如果indexedDB使用更有效的方法,那么一个objectStore可能更有效,这取决于更新三个objectStore之一的对象中的属性值的效率。

我不是贸易程序员;所以,如果我的一些术语是不精确的,我会道歉并且我意识到我的问题是一个相当基本的水平;但是我无法找到任何有关如何" map"一个有效的对象数据库对象。

感谢您阅读我的问题以及您可能提供的任何方向。

编辑/更新:

感谢Josh,他花时间回答我的问题并提供了许多值得思考的项目。我还没有考虑在应用程序的哪些阶段,不同类型的数据写入浏览器存储会影响对象存储数量的确定。

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

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

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

在level_2对象屏幕上,用户通过调用出现在level_2屏幕顶部的窗口,将level_3对象添加到特定的level_2元素。关闭每个窗口时,将执行类似的检查,并将所有数据更改写入存储。

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

除了数据库效率的某些经验法则之外,针对我的特定问题和环境的可能最佳方法是以两种方式对其进行编码,使用大于预期数量的最大模块填充投资组合,以及level_2和level_3对象,并测试写入和读取数据到indexedDB的性能。单个对象存储的第一种方法应该相当容易编码,因为它的设置几乎与localStorage完全相同。使用至少三个对象存储库的第二种方法需要花费更多时间,但对于我在这些领域背景有限的人来说,这可能是一种必要且有价值的学习体验。

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

修改

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

我认为可以正常工作的是多个对象存储:一个对象存储包含每个模块的一个完整对象或组合中的level_1数据,以及一个包含"的三个或四个对象存储的数据子集的对象存储。活性"或仅加载模块。

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

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

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

这应该适用于此应用程序中任何单个模块的预期最大大小;但是如果模块的大小在整个加载时对于浏览器变得太大,那么子集对象存储可以用于填充屏幕;当用户退出模块时,子集可以聚集在一起构建一组完整的模块数据并写入模块对象存储库,就像恢复一样。

当然,如果浏览器由于模块过大而运行得太慢而无法在运行时进行测试,并且当时更改了方法。我的意思是,如果在我测试大型样本模块时,观察到浏览器运行速度太慢,那么第二种方法就需要实现。

我意识到我的特定问题没有响应中列出的项目那么有趣。但是,阅读这些一般概念有助于我更好地理解如何解决我对indexedDB的不那么有趣的使用,并避免在编写简单问题时编写不必要的复杂性。再次感谢。

1 个答案:

答案 0 :(得分:3)

我认为你接受了自己的回答,所以我在这里的回答只是为了推动你。

nosql和传统的sql数据库之间的主要区别在于缺少查询规划。查询计划是sql数据库提供的功能,它接受您的查询,解析它,然后将其转换为查找匹配记录的算法,并在结果集中将它们返回给您。查询计划涉及选择最佳方法,通常是尝试最小化所涉及的步骤数,所涉及的内存量或将经过的时间量。另一方面,你自己使用nosql。您必须成为一夜之间的查询规划专家。

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

为了提高速度,我将应用与规范化和非规范化相同的传统知识。博伊斯 - 科德和普通形式1-5和所有这些。 nosql处于极端非规范化的末端。 '逻辑'您存储的项目的结构无关紧要。使用nosql,您的目标不是一个很好的传统和直观的架构。您的目标是有效地执行存储操作和查询。

因此,要回答这个问题,您必须先对操作进行简单分析。枚举您的应用执行的操作。哪些是最常见的操作?您认为哪个时间最长?通过操作,我不是在讨论这里的低级查询,也不是在nosql / sql中讨论db的模式。这是一个太深的抽象层次。更抽象地思考。枚举类似于"为所有符合这些条件的人加载信息","删除那些人"。我接受了你提到的一些问题,但我没有拿到清单,这个列表是正确答案中的重要标准。

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

也许这是一个很好的思想实验。伪代码将执行实际繁重的功能。您将直接进入问题并确定可能非常慢的函数部分。那么你的问题的答案基本上是数据结构真正加速这些部分,或者至少比其他数据结构减慢速度慢。

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

编辑:这是一个粗略的第一次尝试,只是集思广益,这是一个尝试,根据猜测你想要实际做什么给你一个更具体的答案

你想要的是一个名为' settings'的对象商店。商店中的每个对象都代表一个Settings对象。单个设置对象具有设置ID,设置属性名称,设置属性值,级别1属性,级别2属性,级别3属性等属性。

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

进一步说,您可以使用索引优化某些视图。我们可以在level1属性上创建索引,在level2属性上创建索引,并在level1 + level2属性上创建索引。

让我们说最频繁的操作,需要最快,是加载属于级别1,2和3的特定组合的所有设置。在所有3上创建一个索引,然后它是只是迭代该索引。

这个头脑风暴示例中的架构是单个对象存储,以及一些加速某些查询的索引。鉴于索引基本上是派生对象存储,您可以使概念参数实际上使用多个存储,尽管您实际上只使用了一个。无论如何,这可能会变得迂腐。这个例子的目的只是为了证明对象存储的模式与如何概念化组合和层次的层次结构没有任何关系。它只与进行快速执行所需的查询有关。