如何将多级对象映射到 indexedDB 以获得最佳效率
How to map mulit-level object to indexedDB for best efficiency
我的问题涉及在 indexedDB 中布置数据结构。我开始构建一个小网页功能,后来逐渐发展成为一种网络学习工具,现在更接近于独立的渐进式网络应用程序。使用 localStorage 效果很好,但随着该工具的发展,5MB 的限制对于某些用户来说可能会成为一个问题;因此,需要切换到 indexedDB。
该应用程序仅适用于台式机,允许用户构建模块组合并将数据作为 JSON 字符串保存到硬盘驱动器。当用户在应用程序中打开(上传)文件时,将解析字符串并将整个投资组合再次写入 localStorage,但每次只有一个模块写入 运行-time 对象。 "genuine" 数据库从不同字段查找数据和索引的角度来说是不需要的,只是需要更大的存储量,因为如果每个模块都放在一起,对用户来说太混乱了投资组合必须是一个单独的文件。
保存到localStorage的数据大部分来自三级对象,根据对象路径做key来保存和检索数据。例如 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 可以被视为类似于双列 table,每个单独的数据点都有一行(一个键和一个值),则行数将大于总和三个 objectStores 的行计数,其中每一行都是一个键和多个数据点的对象;但是,要更新三个 objectStore 之一中的单个数据点,必须将数据库对象写入临时对象,更新数据点,然后写回 objectStore。
那么,问题是哪个更有效:在多行中的单个 table 中搜索指向一个不太复杂的值的单个唯一键,或者在三个 table 中搜索一个=]s 的行数较少,但必须执行我认为等效于 JSON 解析、值更新和 JSON 字符串化以更新数据库中相同值的操作?
虽然没有明确设置限制,但单个投资组合中 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 可能会更有效,这取决于更新三个 objectStores 之一的对象中的 属性 值的效率。
我不是职业程序员;所以,如果我的某些术语不准确,我深表歉意,我意识到我的问题是相当基本的;但我一直无法找到任何关于如何 "map" 以有效方式将对象添加到对象数据库的信息。
感谢您阅读我的问题以及您可能提供的任何指导。
编辑/更新:
谢谢你,Josh,感谢你抽出时间回答我的问题,并提供了一些可供思考的项目。我还没有考虑在应用程序的哪些时间点将不同类型的数据写入浏览器存储会如何影响对象存储数量的确定。
有两个大的数据移动通常在用户会话期间每次只发生一次:从硬盘上传一个 JSON 字符串进行解析并写入浏览器存储,然后浏览器读取存储到要字符串化并下载到硬盘的对象中。用户很可能希望这两个步骤至少有足够的时间来要求某种形式的简短进度指示器。重要的时间项是存储数据编辑和创建新数据元素所需的时间。
根据 Josh 的评论,也许设置对象存储的一个好方法是考虑屏幕何时以及将什么数据写入浏览器存储,因为没有更好的术语。在我的应用程序中,任何时候只有一个模块(投资组合中的 level_1 对象)被加载到 运行 时间对象中。模块级数据只有一个屏幕。退出该屏幕时,模块级数据中的任何更改都将写入存储。
模块中的每个 level_2 对象都有自己的屏幕,当用户在 level_2 对象屏幕之间导航时,将根据 运行 检查屏幕输入元素中的内容-time 对象的值更改,任何更改都写入存储。
在 level_2 对象屏幕上,用户通过调用出现在 level_2 顶部的 window 将 level_3 对象添加到特定的 level_2 元素=] 屏幕。当每个 window 关闭时,将执行类似的检查并将任何数据更改写入存储。
创建与每个屏幕上显示和收集的数据对齐的对象存储似乎很有意义,当然,与对象级别对齐。但是,它仍然没有回答哪种数据结构最终会最有效,从而提供最佳的用户体验。
除了某种类型的数据库效率经验法则之外,针对我的特定问题和情况的最佳方法可能是用两种方式进行编码,用大于预期数量的最大模块填充投资组合,并且level_2和level_3对象,测试indexedDB读写数据的性能。单个对象存储的第一个方法应该很容易编写代码,因为它的设置几乎与 localStorage 完全一样。使用至少三个对象存储的第二种方法将花费更多时间,但对于我在这些领域的背景有限的人来说,这可能是必要且值得学习的经验。
如果我成功了,我会在不久的将来在这里分享结果。谢谢。
编辑:
感谢您的进一步解释。我不会以那种方式查询数据库,而是存储数据以仅基于唯一键进行检索。但是,您之前关于在多个 table 中存储相同数据的评论最终在我脑海中浮现,我认为大大简化了我的整个问题和方法。我从本地存储的角度考虑太多了。
我认为可以很好地工作的是多个对象存储:一个对象存储包含每个模块的一个完整对象或 level_1 投资组合中的数据,以及三个或四个包含数据子集的对象存储仅用于 "active" 或加载的模块。
当用户选择要加载的模块时,它将一步从模块对象存储中完整加载,并且该模块的子集(不同对象级别)将写入多个不同的对象存储.当用户在任何级别对模块数据进行编辑时,编辑将存储在适当的子集对象存储中,因为这样会更快。
如果用户正确exits/closes一个模块,那么此时加载的对象将被完整地写入模块对象存储,子集对象存储将被清空。子集对象存储在那里
以在用户无法正常退出或出现电源或 OS 故障时保留更改。
打开应用程序时,将测试浏览器存储以确定是否存在数据库,如果存在,则子集对象存储是否为空。如果为空,则正确关闭并保存模块。如果不为空,那么对模块的编辑无论出于何种原因都没有进入模块对象存储,并且我将提示用户恢复或丢弃保存在子集对象存储中的编辑。如果用户选择恢复,那么子对象存储中的数据必须聚集在一起成为一个完整的模块并写入模块对象存储。
对于此应用程序中任何单个模块的预期最大尺寸,这应该可以正常工作;但是如果一个模块的大小在整个加载时对于浏览器来说太大了,那么可以使用子集对象存储来填充屏幕;当用户退出模块时,可以将子集聚集在一起以构建完整的模块数据集并写入模块对象存储,就像恢复一样。
当然没办法在运行时间内测试浏览器是否因为模块过大导致运行ning太慢,并在那个时候改变方法。我的意思是,如果在我测试大型示例模块时,发现浏览器 运行 速度太慢,则需要实施第二种方法。
我意识到我的特定问题不如答复中列出的项目有趣。然而,阅读这些一般概念帮助我更好地理解如何解决我对 indexedDB 的不太有趣的使用,并避免为一个简单的问题编写不必要的复杂性的大量混乱。再次感谢。
我想你已经有了自己的答案,所以我在这里的回答只是为了推动你前进。
无sql 与传统sql 数据库之间的主要区别是缺少查询计划。查询计划是 sql 数据库提供的功能,它接受您的查询,对其进行解析,然后将其转换为一种算法,该算法可以找到匹配的记录并 returns 在结果集中将它们提供给您。查询规划涉及选择最佳方法,通常是通过尽量减少所涉及的步骤数、所涉及的内存量或将要经过的时间量。另一方面,没有sql,你只能靠自己了。您必须在一夜之间成为查询规划专家。
这既是福音也是负担。查询计划对某些人来说是一个复杂的悬崖,您很快就会发现自己阅读了一些令人困惑的东西。但是,如果您正在寻找更技术性的答案,那么它会朝这个方向发展,即更多地了解数据库如何进行查询规划。
为了加快速度,我将应用有关规范化和反规范化的相同常规知识。 Boyce-Codd 和正常形式 1-5 等等。 nosql 处于极端反规范化端。您存储的项目的 'logical' 结构无关紧要。没有 sql 你的 objective 不是一个很好的传统和直观的模式。您的 objective 是为了高效地执行您的存储操作、您的查询。
所以要回答这个问题你得先简单分析一下你的操作。枚举您的应用执行的操作。哪些操作最频繁?你认为哪一个需要最长的时间才能完成?通过操作,我在这里不是在谈论低级查询,也不是在 nosql/sql 中的数据库模式。这是一个太深的抽象层次。更抽象地思考。枚举 "load the info for all the people that meet these conditions"、"delete those people over there" 之类的东西。我回答了你提到的一些问题,但我没有回答一个明确的列表,这个列表是正确回答的重要标准。
一旦你列举了那些操作,那么我认为你离回答你的问题更近了。作为玩具示例,考虑更新。更新频繁吗?频繁的更新表明一个对象存储是坏的,因为你必须加载大量不相关的东西只是为了改变一个对象的 属性。考虑粒度。您需要一个对象的所有属性,还是只需要其中的一部分?想想最频繁的操作是什么?它是否根据某些标准加载对象列表?它是删除还是更新东西?想想同时加载哪些东西(并置)。当您加载 2 级对象的一个实例时,其他实例通常是否也被加载?如果不是,那为什么要把它们放在一起?远离你的规范化模式,忘记它。您需要一个非规范化模式,您可以在其中以某种方式存储数据以优化您的查询。最终的结果可能跟你想象的完全不一样
也许这是一个很好的思想实验。伪代码将完成实际繁重工作的功能。您将 运行 直接进入问题并确定函数中可能非常慢的部分。那么你的问题的答案本质上是什么数据结构会真正加快这些部分的速度,或者至少比其他数据结构减慢它们的速度。
编辑:一点跟进。 nosql 数据库和非规范化的一个相当违反直觉的特征是您最终可能会多次存储数据。有时将相同的数据存储在多个地方是有意义的。因为它加快了查询速度。是的,它引入了不一致的空间,并且违反了 sql 的无功能依赖规则。但是您可以通过使用多存储事务和一点点小心来加强数据完整性(一致性)。进一步详细说明,您想要的商店可能只是您计划执行的查询的字面结果。是的。为您计划执行的每个查询创建对象存储。在它们之间冗余存储数据。是的,这听起来很疯狂和极端。这有点夸张。但是这种方法很常见,并且在使用 nosql.
时得到推广。
编辑:这是第一次粗略的尝试,只是稍微集思广益,这是一种基于猜测您实际尝试做什么的尝试,为您提供更具体的答案
您想要的是一个名为 'settings' 的对象存储。商店中的每个对象代表一个设置对象。单个设置对象具有设置 ID、设置 属性 名称、设置 属性 值、级别 1 属性、级别 2 属性、级别 3 属性 等属性.
您的基本读取查询可能类似于 SELECT * from Settings WHERE level1 = 'a' && level2 = 'b'
。
更进一步,您可以使用索引优化某些视图。我们可以在 level1 属性 上创建索引,在 level2 属性 上创建索引,并在 level1+level2 属性上创建索引。
假设您最频繁的操作(需要最快)是加载属于第 1、2 和 3 级特定组合的所有设置。在所有 3 个上创建一个索引,然后它只是一个迭代该索引的问题。
此头脑风暴示例中的模式是单个对象存储,以及一些索引以加快某些查询。鉴于索引基本上是派生的对象存储,您可以提出概念上的论点,即您实际上使用了多个存储,尽管您实际上只使用了一个。无论如何,这可能会变得迂腐。这个例子的目的只是为了证明您的对象存储的模式与您如何概念化投资组合和级别的层次结构完全无关。它只与使您需要快速执行的查询有关。
我的问题涉及在 indexedDB 中布置数据结构。我开始构建一个小网页功能,后来逐渐发展成为一种网络学习工具,现在更接近于独立的渐进式网络应用程序。使用 localStorage 效果很好,但随着该工具的发展,5MB 的限制对于某些用户来说可能会成为一个问题;因此,需要切换到 indexedDB。
该应用程序仅适用于台式机,允许用户构建模块组合并将数据作为 JSON 字符串保存到硬盘驱动器。当用户在应用程序中打开(上传)文件时,将解析字符串并将整个投资组合再次写入 localStorage,但每次只有一个模块写入 运行-time 对象。 "genuine" 数据库从不同字段查找数据和索引的角度来说是不需要的,只是需要更大的存储量,因为如果每个模块都放在一起,对用户来说太混乱了投资组合必须是一个单独的文件。
保存到localStorage的数据大部分来自三级对象,根据对象路径做key来保存和检索数据。例如 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 可以被视为类似于双列 table,每个单独的数据点都有一行(一个键和一个值),则行数将大于总和三个 objectStores 的行计数,其中每一行都是一个键和多个数据点的对象;但是,要更新三个 objectStore 之一中的单个数据点,必须将数据库对象写入临时对象,更新数据点,然后写回 objectStore。
那么,问题是哪个更有效:在多行中的单个 table 中搜索指向一个不太复杂的值的单个唯一键,或者在三个 table 中搜索一个=]s 的行数较少,但必须执行我认为等效于 JSON 解析、值更新和 JSON 字符串化以更新数据库中相同值的操作?
虽然没有明确设置限制,但单个投资组合中 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 可能会更有效,这取决于更新三个 objectStores 之一的对象中的 属性 值的效率。
我不是职业程序员;所以,如果我的某些术语不准确,我深表歉意,我意识到我的问题是相当基本的;但我一直无法找到任何关于如何 "map" 以有效方式将对象添加到对象数据库的信息。
感谢您阅读我的问题以及您可能提供的任何指导。
编辑/更新:
谢谢你,Josh,感谢你抽出时间回答我的问题,并提供了一些可供思考的项目。我还没有考虑在应用程序的哪些时间点将不同类型的数据写入浏览器存储会如何影响对象存储数量的确定。
有两个大的数据移动通常在用户会话期间每次只发生一次:从硬盘上传一个 JSON 字符串进行解析并写入浏览器存储,然后浏览器读取存储到要字符串化并下载到硬盘的对象中。用户很可能希望这两个步骤至少有足够的时间来要求某种形式的简短进度指示器。重要的时间项是存储数据编辑和创建新数据元素所需的时间。
根据 Josh 的评论,也许设置对象存储的一个好方法是考虑屏幕何时以及将什么数据写入浏览器存储,因为没有更好的术语。在我的应用程序中,任何时候只有一个模块(投资组合中的 level_1 对象)被加载到 运行 时间对象中。模块级数据只有一个屏幕。退出该屏幕时,模块级数据中的任何更改都将写入存储。
模块中的每个 level_2 对象都有自己的屏幕,当用户在 level_2 对象屏幕之间导航时,将根据 运行 检查屏幕输入元素中的内容-time 对象的值更改,任何更改都写入存储。
在 level_2 对象屏幕上,用户通过调用出现在 level_2 顶部的 window 将 level_3 对象添加到特定的 level_2 元素=] 屏幕。当每个 window 关闭时,将执行类似的检查并将任何数据更改写入存储。
创建与每个屏幕上显示和收集的数据对齐的对象存储似乎很有意义,当然,与对象级别对齐。但是,它仍然没有回答哪种数据结构最终会最有效,从而提供最佳的用户体验。
除了某种类型的数据库效率经验法则之外,针对我的特定问题和情况的最佳方法可能是用两种方式进行编码,用大于预期数量的最大模块填充投资组合,并且level_2和level_3对象,测试indexedDB读写数据的性能。单个对象存储的第一个方法应该很容易编写代码,因为它的设置几乎与 localStorage 完全一样。使用至少三个对象存储的第二种方法将花费更多时间,但对于我在这些领域的背景有限的人来说,这可能是必要且值得学习的经验。
如果我成功了,我会在不久的将来在这里分享结果。谢谢。
编辑:
感谢您的进一步解释。我不会以那种方式查询数据库,而是存储数据以仅基于唯一键进行检索。但是,您之前关于在多个 table 中存储相同数据的评论最终在我脑海中浮现,我认为大大简化了我的整个问题和方法。我从本地存储的角度考虑太多了。
我认为可以很好地工作的是多个对象存储:一个对象存储包含每个模块的一个完整对象或 level_1 投资组合中的数据,以及三个或四个包含数据子集的对象存储仅用于 "active" 或加载的模块。
当用户选择要加载的模块时,它将一步从模块对象存储中完整加载,并且该模块的子集(不同对象级别)将写入多个不同的对象存储.当用户在任何级别对模块数据进行编辑时,编辑将存储在适当的子集对象存储中,因为这样会更快。
如果用户正确exits/closes一个模块,那么此时加载的对象将被完整地写入模块对象存储,子集对象存储将被清空。子集对象存储在那里 以在用户无法正常退出或出现电源或 OS 故障时保留更改。
打开应用程序时,将测试浏览器存储以确定是否存在数据库,如果存在,则子集对象存储是否为空。如果为空,则正确关闭并保存模块。如果不为空,那么对模块的编辑无论出于何种原因都没有进入模块对象存储,并且我将提示用户恢复或丢弃保存在子集对象存储中的编辑。如果用户选择恢复,那么子对象存储中的数据必须聚集在一起成为一个完整的模块并写入模块对象存储。
对于此应用程序中任何单个模块的预期最大尺寸,这应该可以正常工作;但是如果一个模块的大小在整个加载时对于浏览器来说太大了,那么可以使用子集对象存储来填充屏幕;当用户退出模块时,可以将子集聚集在一起以构建完整的模块数据集并写入模块对象存储,就像恢复一样。
当然没办法在运行时间内测试浏览器是否因为模块过大导致运行ning太慢,并在那个时候改变方法。我的意思是,如果在我测试大型示例模块时,发现浏览器 运行 速度太慢,则需要实施第二种方法。
我意识到我的特定问题不如答复中列出的项目有趣。然而,阅读这些一般概念帮助我更好地理解如何解决我对 indexedDB 的不太有趣的使用,并避免为一个简单的问题编写不必要的复杂性的大量混乱。再次感谢。
我想你已经有了自己的答案,所以我在这里的回答只是为了推动你前进。
无sql 与传统sql 数据库之间的主要区别是缺少查询计划。查询计划是 sql 数据库提供的功能,它接受您的查询,对其进行解析,然后将其转换为一种算法,该算法可以找到匹配的记录并 returns 在结果集中将它们提供给您。查询规划涉及选择最佳方法,通常是通过尽量减少所涉及的步骤数、所涉及的内存量或将要经过的时间量。另一方面,没有sql,你只能靠自己了。您必须在一夜之间成为查询规划专家。
这既是福音也是负担。查询计划对某些人来说是一个复杂的悬崖,您很快就会发现自己阅读了一些令人困惑的东西。但是,如果您正在寻找更技术性的答案,那么它会朝这个方向发展,即更多地了解数据库如何进行查询规划。
为了加快速度,我将应用有关规范化和反规范化的相同常规知识。 Boyce-Codd 和正常形式 1-5 等等。 nosql 处于极端反规范化端。您存储的项目的 'logical' 结构无关紧要。没有 sql 你的 objective 不是一个很好的传统和直观的模式。您的 objective 是为了高效地执行您的存储操作、您的查询。
所以要回答这个问题你得先简单分析一下你的操作。枚举您的应用执行的操作。哪些操作最频繁?你认为哪一个需要最长的时间才能完成?通过操作,我在这里不是在谈论低级查询,也不是在 nosql/sql 中的数据库模式。这是一个太深的抽象层次。更抽象地思考。枚举 "load the info for all the people that meet these conditions"、"delete those people over there" 之类的东西。我回答了你提到的一些问题,但我没有回答一个明确的列表,这个列表是正确回答的重要标准。
一旦你列举了那些操作,那么我认为你离回答你的问题更近了。作为玩具示例,考虑更新。更新频繁吗?频繁的更新表明一个对象存储是坏的,因为你必须加载大量不相关的东西只是为了改变一个对象的 属性。考虑粒度。您需要一个对象的所有属性,还是只需要其中的一部分?想想最频繁的操作是什么?它是否根据某些标准加载对象列表?它是删除还是更新东西?想想同时加载哪些东西(并置)。当您加载 2 级对象的一个实例时,其他实例通常是否也被加载?如果不是,那为什么要把它们放在一起?远离你的规范化模式,忘记它。您需要一个非规范化模式,您可以在其中以某种方式存储数据以优化您的查询。最终的结果可能跟你想象的完全不一样
也许这是一个很好的思想实验。伪代码将完成实际繁重工作的功能。您将 运行 直接进入问题并确定函数中可能非常慢的部分。那么你的问题的答案本质上是什么数据结构会真正加快这些部分的速度,或者至少比其他数据结构减慢它们的速度。
编辑:一点跟进。 nosql 数据库和非规范化的一个相当违反直觉的特征是您最终可能会多次存储数据。有时将相同的数据存储在多个地方是有意义的。因为它加快了查询速度。是的,它引入了不一致的空间,并且违反了 sql 的无功能依赖规则。但是您可以通过使用多存储事务和一点点小心来加强数据完整性(一致性)。进一步详细说明,您想要的商店可能只是您计划执行的查询的字面结果。是的。为您计划执行的每个查询创建对象存储。在它们之间冗余存储数据。是的,这听起来很疯狂和极端。这有点夸张。但是这种方法很常见,并且在使用 nosql.
时得到推广。编辑:这是第一次粗略的尝试,只是稍微集思广益,这是一种基于猜测您实际尝试做什么的尝试,为您提供更具体的答案
您想要的是一个名为 'settings' 的对象存储。商店中的每个对象代表一个设置对象。单个设置对象具有设置 ID、设置 属性 名称、设置 属性 值、级别 1 属性、级别 2 属性、级别 3 属性 等属性.
您的基本读取查询可能类似于 SELECT * from Settings WHERE level1 = 'a' && level2 = 'b'
。
更进一步,您可以使用索引优化某些视图。我们可以在 level1 属性 上创建索引,在 level2 属性 上创建索引,并在 level1+level2 属性上创建索引。
假设您最频繁的操作(需要最快)是加载属于第 1、2 和 3 级特定组合的所有设置。在所有 3 个上创建一个索引,然后它只是一个迭代该索引的问题。
此头脑风暴示例中的模式是单个对象存储,以及一些索引以加快某些查询。鉴于索引基本上是派生的对象存储,您可以提出概念上的论点,即您实际上使用了多个存储,尽管您实际上只使用了一个。无论如何,这可能会变得迂腐。这个例子的目的只是为了证明您的对象存储的模式与您如何概念化投资组合和级别的层次结构完全无关。它只与使您需要快速执行的查询有关。