数据库-索引表设计

索引表模式

基于数据存储中经常由查询引用的字段创建索引。 此模式可让应用程序更快地找到要从数据存储中检索的数据,从而提高了查询性能。

上下文和问题

许多数据存储使用主键组织实体集合的数据。 应用程序可以使用此键来查找和检索数据。 下图显示了一个保存客户信息的数据存储示例。 主键为“客户 ID”。 该图显示按主键(“客户 ID”)组织的客户信息。

图 1 - 按主键(“客户 ID”)组织的客户信息。

尽管主键对于基于此键值提取数据的查询很有作用,但如果应用程序需要基于其他某个字段检索数据,则可能无法使用主键。 在客户示例中,如果应用程序只是通过引用其他某个属性(例如,客户所在的城镇)的值查询数据,则无法使用“客户 ID”主键来检索客户。 若要执行此类查询,应用程序可能需要提取并检查每条客户记录,而这是一个缓慢的过程。

许多关系数据库管理系统支持辅助索引。 辅助索引是按一个或多个非主键(辅助键)字段组织的独立数据结构,指示每个索引值的数据的存储位置。 辅助索引中的项通常已按辅助键的值排序,以便快速查找数据。 这些索引通常由数据库管理系统自动维护。

可以根据需要创建任意数目的辅助索引来支持应用程序执行的不同查询。 例如,在关系数据库中“客户 ID”是主键的“客户”表中,如果应用程序经常根据客户所在的城镇查找客户,则基于城镇字段添加一个辅助索引会很有作用。

尽管辅助索引在关系数据库系统中很常见,但云应用程序使用的部分 NoSQL 数据存储并不提供等效的功能。

解决方案

如果数据存储不支持辅助索引,你可以通过创建自己的索引表来手动模拟辅助索引。 索引表按指定的键组织数据。 根据所需的辅助索引数目以及应用程序执行的查询性质,我们通常可以使用三种策略来构建索引表。

第一种策略是复制每个索引表中的数据,但按不同的键(完全非规范化)组织这些数据。 下图显示了按“城镇”和“姓氏”组织相同客户信息的索引表。

规范化:关系模型要求关系必须是规范化的,即要求关系必须满足一定的规范条件,其中最基本的一条就是,关系的每一个分量必须是一个不可分割的数据项,也就是说,不允许表中有表。
ps:这个事实表已然是非规范化的,用户信息分量中包含了太多维度的信息了,是一个可分割的数据项。

图 2 - 已复制每个索引表中的数据

如果相对于使用每个键查询数据的次数而言数据较为静态,则此策略适用。 如果数据较为动态,则维护每个索引表所产生的处理开销会变得过大,因此,此方法并不有效。 此外,如果数据量极大,存储重复数据所需的空间量也很大。

第二种策略是创建按不同键组织的规范化索引表,并使用主键来引用原始数据,而不是复制数据,如下图所示。 原始数据称为事实数据表。

图 3 - 数据由每个索引表引用

此方法可节省空间,并减少维护重复数据所产生的开销。 其缺点在于,应用程序必须使用辅助键执行两项查找操作才能找到数据。 应用程序必须在索引表中查找数据的主键,然后使用主键来查找事实数据表中的数据。

第三种策略是创建按不同键组织的、可复制经常检索的字段的部分规范化索引表。 引用事实数据表即可访问不经常访问的字段。 下图显示了如何在每个索引表中复制经常访问的数据。

图 4 - 在每个索引表中复制经常访问的数据。

如果使用此策略,可以在前两种方法之间取得平衡。 可以使用单个查找操作快速检索用于常见查询的数据,同时,空间和维护开销不如复制整个数据集那么大。

如果应用程序经常通过指定值的组合(例如,“查找居住在 Redmond,且姓氏为 Smith 的所有客户”)查询数据,则你可以针对索引表中的项实现“城镇”属性与“姓氏”属性串联形式的键。 下图显示了基于组合键的索引表。 这些键先按“城镇”排序,然后,针对具有相同“城镇”值的记录按“姓氏”排序。

图 5 - 基于组合键的索引表

索引表可以加速分片数据的查询操作,当分片键已经过哈希处理时特别有用。 下图显示了将“客户 ID”哈希用作分片键的示例。 索引表可按未经哈希处理的值(“城镇”和“姓氏”)组织数据,并提供经过哈希处理的分片键作为查找数据。 这样,在应用程序需要检索处于某个范围内的数据,或者需要根据未经哈希处理的键的顺序提取数据时,就无需反复计算哈希键(此操作的开销较高)。 例如,通过在索引表中找到匹配项(所有项存储在相邻的块中),可以快速解决类似于“查找居住在 Redmond 的所有客户”的查询。 然后,可以使用索引表中存储的分片键,遵循对客户数据的引用进行操作。

图 6 - 提供快速分片数据查找的索引表

问题和注意事项

在决定如何实现此模式时,请考虑以下几点:

  • 维护辅助索引所产生的开销可能很大。 必须分析并了解应用程序使用的查询。 仅当应用程序定期使用索引表时,才创建索引表。 不要创建推理索引表来支持应用程序不会执行或只是偶尔执行的查询。
  • 复制索引表中的数据可能会明显增大存储成本方面的开销,以及维护多个数据副本所需的工作量。
  • 将索引表实现为引用原始数据的规范化结构需要应用程序执行两项查找操作才能找到数据。 第一项操作搜索索引表以检索主键,第二项操作使用主键提取数据。
  • 如果系统基于极大型数据集整合了许多索引表,则可能很难维持索引表与原始数据之间的一致性。 也许可以围绕最终一致性模型设计应用程序。 例如,若要插入、更新或删除数据,应用程序可以向队列发布一条消息,让单独的任务执行该操作,并维护以异步方式引用此数据的索引表。 有关实施最终一致性的详细信息,请参阅数据一致性入门
  • 索引表本身可能已分区或分片。

何时使用此模式

如果应用程序经常需要使用除主键(或分片键)以外的某个键检索数据,则使用此模式可提高查询性能。

在以下情况下,此模式可能不起作用:

  • 数据是易失性的。 索引表可能很快过时,因此变得低效,或者维护索引表所产生的开销超过了它所带来的成本节省。
  • 选作索引表辅助键的字段没有辨别性,可能只包含少量的值(例如性别)。
  • 选作索引表辅助键的字段的数据值平衡性极度偏斜。 例如,如果 90% 的记录包含某个字段中的相同值,那么,基于此字段创建并维护用于查找数据的索引表所产生的开销,可能比依序搜索整个数据更高。 但是,如果查询经常性地以剩余 10% 的记录中的值为目标,则此索引可能非常有用。 应该了解应用程序执行的查询以及执行这些查询的频率。

参考文档:

索引表模式