分享至

简介

数据库相对于其他更简单的数据存储选项的主要优势之一是它们能够以有序、易于查询的结构存储信息。这些特性源于数据库实现 Schema 来描述它们存储的数据。

一个 数据库 Schema 充当数据库中数据形状和格式的蓝图。对于关系型数据库,这包括通过表、主键、数据类型、索引和其他对象描述数据类别及其连接。对于 NoSQL Schema,这通常涉及根据最重要的预期查询模式组织数据。

无论哪种情况,了解数据库 Schema 的价值以及如何根据您的需求最佳地设计和优化它都至关重要。本指南将重点介绍什么是数据库 Schema、您可能遇到的不同 Schema 类型、它们为何重要以及在设计自己的 Schema 时应牢记哪些事项。

为什么数据库 Schema 很重要?

数据库 Schema 很重要,原因有很多。

无论数据来源或应用如何,您的数据几乎总是包含一些规律性。有些数据高度规律,这意味着它们都可以用相同的模式来描述。有些数据则更不规律,但即便如此,其元数据(关于数据本身的上下文数据)通常仍然是规律的。

数据库 Schema 告诉数据库您的数据是什么以及如何处理它。数据库 Schema 帮助数据库引擎理解这些模式,从而使其能够对数据强制执行约束、在查询时响应正确的信息,并以用户请求的方式对其进行操作。

良好的 Schema 倾向于减少隐式信息,转而使其对系统及其用户可见。关系型数据库中的 Schema 可以减少信息冗余,确保数据一致性,并提供访问和连接相关数据所需的脚手架和结构。在非关系型上下文中,良好的 Schema 通过将存储格式与对应用程序至关重要的访问模式对齐,从而实现高性能和可伸缩性。

定义物理 Schema 与逻辑 Schema

在我们深入之前,我们应该介绍一些定义。两个可能令人困惑的术语是物理 Schema逻辑 Schema。这两个术语根据其使用的上下文可以传达不同的含义。

就本文而言,我们主要讨论设计数据库 Schema 时的逻辑 Schema 和物理 Schema。

设计数据库 Schema 时

在谈论设计数据库 Schema 时,一个 逻辑 Schema 是一种将数据组织成不同类别、定义数据属性并确定数据库项目最佳结构的通用设计。此通用文档不包含实现细节,因此与平台无关。它可以作为蓝图并在各种数据库系统中实现。

在相同的上下文中,一个 物理 Schema 被认为是设计过程中的下一步,其中解决了特定于实现的细节。不同实体、约束、键、索引和其他项的名称被识别并映射到逻辑 Schema。这为使用给定数据库平台进行实现提供了具体的计划。

在此上下文中,逻辑 Schema 和物理 Schema 是设计过程的不同阶段。该过程的目标是根据一组需求迭代地开发实施计划,首先阐述数据的抽象特性,然后将该组织映射到您想要使用的数据库系统的工具集和语言。

讨论数据库架构时

物理 Schema 和逻辑 Schema 有时在数据库中出现的另一个上下文是实际数据库软件的物理和虚拟架构。

在此上下文中,逻辑 Schema 指的是用户与之交互的可见数据库实体。这意味着表、键、视图和索引等对象是用户使用数据库软件创建和操作的抽象。这些项目在系统中的布局是数据库呈现的逻辑 Schema 的一部分。

在相同的上下文中,物理 Schema 指的是数据库软件在与文件系统交互时处理数据、文件和存储的方式。例如,数据库架构的物理 Schema 可以决定系统是为每个数据库还是每个表存储单独的文件,并决定如何将这些文件分区到多个服务器上。

静态 Schema 与动态 Schema

另一种重要的分类可以帮助澄清关系型数据库和非关系型数据库中 Schema 之间的差异,即静态 Schema 和动态 Schema 之间的差异。

静态 Schema 是通常与关系型数据库关联的 Schema 类型。它们预先定义为数据必须遵循的形状,才能被系统接受。当使用静态 Schema 时,数据库系统能够强制执行这些模式,因为静态 Schema 是数据库系统可以根据输入进行验证的期望状态的断言。

相反,动态 Schema 在非关系型上下文中更为普遍。动态 Schema 的灵活性更高,可能缺乏任何预先构思的组织结构。相反,动态 Schema 是根据输入到系统中的数据特性演变而来的。虽然许多非关系型数据库可以存储具有任意内部结构的信息,但在大多数实际用例中,通常会出现规律的模式。

由于动态 Schema 是演变而来的结构,数据库系统不能将其用作一致性工具。然而,作为用户,理解并围绕它们进行开发仍然至关重要。从总体上理解您的数据将是什么样子,以及您的应用程序将如何与它交互,将帮助您选择满足您的需求、性能良好并避免不必要不一致的结构。

设计数据库 Schema

现在您已经了解了不同类型的数据库 Schema,那么如何为您的项目设计一个呢?设计有效的 Schema 需要思考和实践,以及对问题域和将使用数据的系统有透彻的理解。

设计过程因您设计的数据库类型而异。具体来说,静态 Schema 的设计过程与动态 Schema 的设计过程不同。实际上,这些差异最终与关系型数据库(静态)和非关系型数据库(动态)的设计差异相一致。

一般提示

尽管关系型数据库和非关系型数据库的 Schema 设计存在差异,但有一些通用提示适用于任何 Schema 开发。由于其中许多对设计过程的开始很重要,因此有必要先讨论这些。

了解您的数据

设计 Schema 的第一步应该始终是了解您的数据和领域。如果不了解数据库将管理的信息及其使用环境,就不可能开发出良好的数据库设计。

尽管您一开始可能不会了解所有数据特性,但尽可能多地了解系统预期管理的数据对于设计至关重要。

您应该尝试回答的一些问题包括

  • 广义上讲,数据会是什么?
  • 哪些属性是重要的,需要记录?
  • 您的总数据集会有多大?
  • 系统积累新数据的速度会有多快?
  • 您的数据会高度规律吗?

了解使用模式

同样,不了解用户需求就设计数据库 Schema,就像其他软件设计一样,会带来问题。如果您不是数据使用领域的专家,则需要咨询相关人士来指导您了解需求。

您应该问自己以下问题:

  • 最常见的查询可预测吗?
  • 将会有多少并发用户或客户端?
  • 典型操作和查询会触及多少数据?
  • 大多数请求是读查询还是写查询?
  • 哪些数据会定期一起查询?
  • 大多数操作是针对单个记录还是聚合多条记录?

制定命名约定

尽管这看起来可能不重要,但设计一套命名约定并严格遵循它将有助于开发和日常使用。

命名和样式约定有助于最大限度地减少在命名新实体时需要进行的脑力劳动。同样,约定允许用户在访问 Schema 中不同项目时安全地假定一种模式。某些数据库系统或数据库类型已经有流行的命名约定,您可以遵循这些约定以避免意外,并避免需要开发自己的标准。

您可能需要考虑的一些样式和命名约定:

  • 对于区分大小写的系统,您应该如何使用大写和小写字母?
  • 项目何时应使用单词的复数形式,何时应使用单数形式?
  • 多词名称应该用下划线、破折号还是其他分隔符分隔单词?
  • 应该总是使用全名,还是在某些情况下允许使用缩写?

为关系型数据库设计 Schema

关系型数据库通常被认为是灵活的通用解决方案。它们处理即席查询的能力使得同一个数据库可以服务于不同的应用程序和用例。因此,在为关系型数据库设计 Schema 时,您的最终目标通常是以一种促进灵活性同时最大限度地减少数据不一致进入系统的机会的方式来表示您的数据。

开发逻辑 Schema

关系型 Schema 设计通常从逻辑 Schema 开始,如上一节所述。

您需要规划要管理的数据项、它们之间的关系以及需要考虑的任何重要属性,而无需考虑实现细节或性能标准。这一步很重要,因为它将您的所有数据项收集在一个地方,并允许您在抽象层面上理清它们之间的关系。

您可以开始草绘表示特定数据项及其属性的表。这种映射过程通常最好通过实体关系(或 ER)模型来表示。ER 模型是通过定义项目类型及其属性,然后连接这些来映射关系和依赖项的图表,以直观地表示数据对象。

ER 模型经常用于早期 Schema 设计,因为它们非常擅长帮助您弄清楚您拥有哪些不同的实体、必须管理哪些属性、哪些实体相互关联以及它们关系的具体性质。使用 ER 模型图来表示您的逻辑 Schema 可以为您提供一个坚实的计划,说明您希望您的数据库设计是什么样子,而无需评论特定于实现的细节。

开发物理 Schema

一旦您有了逻辑 Schema,下一步就是通过创建物理 Schema 来确定具体的实现细节(如上一节所述)。物理 Schema 将精确地决定您希望如何使用可用的数据库结构和功能来提交您的计划。

第一步通常是遍历每个数据库实体并确定其主键字段。主键用于唯一标识表中的每条记录,以及将不同表中的记录绑定在一起。当逻辑 Schema 中存在两个实体之间的关系时,您需要在物理 Schema 中通过将一个表中的主键作为另一个表中的外键来引用,从而连接这两个表。这种关系的方向将影响您在使用数据库时连接不同实体的性能和便捷性。

在此阶段您还需要考虑的另一个问题是预测的查询模式。这些表中的某些表和字段的访问频率将远高于其他表和字段。这些“热点”是数据库索引的良好候选。数据库索引显著加快了常用项目的检索速度,但代价是数据更新期间性能会变差。确定最初要索引哪些列将帮助您平衡这些考虑因素,并定义系统中索引最关键的位置。

规范化您的数据结构

在此过程中,您可能会发现将逻辑实体中的某些元素提取到其自己的独立表中会更容易。例如,您可能希望从客户中提取送货地址,以便多个送货地址可以与单个客户关联,并且产品订单可以引用特定地址。这些更改可以被认为是称为范式化的过程的一部分。

数据库范式化是一个确保您的数据库仅表示每条数据一次且不允许导致不一致的更新的过程。范式化是一个巨大的话题,在很大程度上超出了本指南的范围,但物理 Schema 设计过程的一部分涉及确定要追求的范式化级别,并根据需要转换数据实体以实现该目标。

为非关系型和 NoSQL 数据库设计 Schema

非关系型数据库的设计过程通常大相径庭。这种差异很大程度上源于非关系型数据库通常被选择是为了在有限数量的预定义查询上实现高性能。

确定您的主要查询

非关系型数据库的 Schema 通常与将使用它们的应用程序协同设计。Schema 反映了应用程序的特定需求,从某种意义上说,它是一种定制结构,旨在适应应用程序开发的模式。

由于这种密切关系,确定您的数据库必须优化以响应哪些查询非常重要。第一步是弄清楚您的数据库需要运行哪些查询。由于您还没有数据结构,这些将是伪查询,但了解您的应用程序需要哪些数据才能执行某些操作是您的首要目标。

一旦您对应用程序需要执行的查询有了很好的了解,您就需要选择最重要的查询进行关注。这些是您的应用程序经常执行且不能等待的查询。

确定哪些查询最重要,可以告诉您数据结构需要优化的确切访问模式。数据库系统存储和表示数据的方式将对其快速检索和操作数据项的能力产生巨大影响。

围绕您的主要查询设计初始 Schema

现在您已经了解了最基本的访问模式,您就可以开始开发与这些查询相匹配的 Schema。

此过程的第一步是确定每个查询需要返回的精确信息。然后,规划将所有信息存储在单个实体中以响应查询的样子。

例如,如果您的应用程序将查询数据库以检索用户个人资料信息,您的起点很可能是假设所有用户个人资料信息都可以存储在一个位置。

尽可能合并和去重数据实体

确定了所需的属性并规划了将与每个查询相关的所有项目存储在单个实体中的样子后,检查是否存在重叠。这样做的目的是尽可能整合数据实体,以减少系统需要维护的独立项目数量。您维护的独立实体类型越多,出现不一致和更新性能问题的可能性就越大。

其中一些重叠会相当明显。一个查询返回的属性是另一个查询属性的子集的情况可以安全地合并到单个实体中。

其他时候,确定如何映射查询信息可能更困难。非关系型数据库通常不擅长在单个查询中合并来自多个实体的数据,而关系型数据库通过联接在此方面表现出色。因此,当某些属性或实体存在于多个查询中时,您可能需要选择最佳的数据表示方式。

确定您的应用程序可以在何处填补空白

对于某些查询,您的应用程序可能需要承担部分数据组装工作,而不是依赖数据库在单个查询中响应所有相关信息。例如,如果您需要处理客户信息及其相关订单,将订单存储在不同的类别中并在客户对象中通过 ID 引用它们可能更合理。

某些数据库系统无法通过遵循对象之间的引用轻松连接此信息。相反,您的应用程序可能需要首先查询客户,然后使用您发现的订单 ID 对每个相关订单进行额外查询。

在您的应用程序代码中执行这些操作有助于规避某些非关系型数据库的限制。这通常是比试图在单个条目中维护大量信息或试图为许多不同类型的数据库对象多次复制数据更好的选择。这些选项可能会导致非常差的性能和数据一致性。

话虽如此,一旦应用程序代码和数据库 Schema 都上线,测试和调优它们将非常重要,以确保您不会为了快速的数据库操作而牺牲良好的应用程序性能。一个好的经验法则是尽可能利用数据库的功能,因为它们针对信息检索和操作进行了高度优化,并根据需要在应用程序中进行补充。

确定合适的分区键

对于高度可扩展的非关系型数据库,用户通常需要确定分区或分片键。这些键将用于在不同服务器之间拆分数据集,以提高性能和响应能力。

找到合适的分区键高度依赖于您的数据和工作负载。然而,一些通用规则可以为您提供指导。

最好尝试选择具有相当均匀的键分布的分区键。例如,如果您需要分发客户数据,他们的出生月份通常会导致不错的分布。相反,如果您正在销售冬季服装,注册月份就不会是一个好的分区键,因为您的产品的季节性可能会影响键的分布。对候选数据应用哈希算法有时也有助于更均匀地分布您的键空间。

另一个考虑因素是您的工作负载是读密集型还是写密集型。如果您的应用程序是读密集型的,您可能希望选择一个分区键,该键允许您将尽可能多的相关数据写入单个服务器。这将有助于您避免每次需要检索相关数据时都必须从多个服务器读取。

另一方面,如果您的工作负载是写密集型的,通常更倾向于将写入分散到尽可能多的服务器上。如果每个请求最终都将数据写入同一台服务器,那么您将不会在写密集型操作中获得太多性能提升。

总结

设计有效的数据库 Schema 需要耐心、实践,并且通常需要大量的试错。

首先,您必须尝试很好地了解您的数据将是什么样子、您的应用程序将如何使用它以及需要哪些可用性和数据完整性要求。之后,您的目标是开发一个能够反映您的数据特定特征并促进您预期的用例类型的 Schema。

Schema 设计与其他任何类型的设计一样,是一个迭代过程。随着您对问题域理解的加深以及实际性能数据的可用,预计会更改您的设计。虽然您可能需要随着时间的推移不断发展您的 Schema,但从坚实的基础开始将有助于此过程,并降低未来发生重大、破坏性 Schema 更改的可能性。

关于作者
Justin Ellingwood

Justin Ellingwood

Justin 自 2013 年以来一直撰写有关数据库、Linux、基础设施和开发者工具的文章。他目前与妻子和两只兔子住在柏林。他通常不必用第三人称写作,这对所有相关方来说都是一种解脱。
© . All rights reserved.