分享到

导言

将表格称为关系数据库的“基本构建块”有点过于简化。虽然在列出您感兴趣的每个数据,并声明这是一个整数,那是一个日期等等时,很难有太大偏差,但有效关系数据库设计首先取决于经过深思熟虑和良好制定的表格设计。表格必须对用户和开发人员清晰易懂,其模式能够理解它们所代表的信息。虽然数据库在很大程度上可以不受监督地处理物理存储,但了解此过程的工作原理并为此进行设计对于包含许多大值的记录的“宽”表尤为重要。

通常,但并非总是,一个表将单个概念的各个方面分组:一个人的姓名和密码,酒店预订的入住和退房日期,或货运的来源、目的地、状态和跟踪号。确定一个表应该在哪里结束而另一个表应该在哪里开始的艺术和科学称为规范化,我们将在未来的文章中介绍它。

无效的表格设计形式多种多样。它们可能无法捕获表格所代表主题的重要特征,对 或记录中被视为有效的值限制过多或不足,在多个列中“拆分数据”,或合并或连接在数据库外部单独处理的方面。即使是没有重大遗漏或结构缺陷的表格也可能存在令人困惑、误导或过时的列名问题。

命名事物

关于如何处理 命名的细节 在数据库中 的观点 比比皆是。没有人会在所有观点上达成一致,并且一个人对单数或复数表名(或者,就此而言,关键字大写或小写)的忠诚度在很大程度上是习惯的功能。RDBMS 的风格也起作用,PostgreSQL 非常偏爱 snake_case 名称,而 UpperCamelCase 是 SQL Server 的明显标志。

就我而言,我使用 snake_case,布尔值以 is_ 为前缀,日期以 _at 为后缀,表格使用复数,列使用单数,连接表命名为 leader_followers。但是,正如任何值得倾听的指南都会告诉您的那样:最重要的事情不是您是否像我一样做,而是您是否与自己的偏好或您正在使用的现有数据库的约定保持一致。

存储:用 1 和 0 制作记录

大多数情况下,现代 RDBMS 抽象了数据如何排列和存储的大部分复杂细节。存储的基本单位是,这是一个大小统一的逻辑块,包含记录和元数据,对应于物理存储空间的一部分:Oracle 中为 2kb,DB2 中为 4kb,SQL Server 和 PostgreSQL 中为 8kb,MySQL 的 InnoDB 中为 16kb。当数据库读取或写入数据时,它是按页而不是按记录进行的。

Layout of metadata and multiple rows on a database page.

一个 元组,即表示给定记录或行的属性-值对,有可能超出页面中可用空间减去元数据。只有 Oracle 和 SQL Server 可以将单个元组分布在多个页面上,而 MySQL 和 DB2 可以配置页面大小。PostgreSQL 仅允许在编译时配置页面大小,但使用 “超大属性存储技术”或 TOAST 将长值移出页面并在其位置记录指针。

所有这些技术都会产生性能成本,因为 SQL 语句读取或写入的页面越少,或者跟踪的指针越少,速度就越快。但是,页面分配无效还会以另一种方式损害性能:当表的大部分或全部元组仅占用半页多一点时,剩余空间将不会被使用。因为它仍然被分配,所以会使表的磁盘使用量膨胀,并且检索或影响 n 行的查询必须加载所有 n 页。

除了 Postgres 之外,大多数企业关系数据库还以为单位管理页面,并依次在下对区进行分组。这种更复杂的策略有助于保持行有序,从而可以加快搜索速度。Postgres 在这方面有自己的技巧,例如使用 可见性映射 来支持“仅索引查询”,这些查询完全跳过查看表的页面。索引本身也存储在页面中,尽管这些页面有自己的组织结构。

像 Cassandra 和 HBase 这样的面向列的数据库的主要创新在于放弃了逐元组的物理页面布局,以加快按列检索大量数据的速度。然而,这绝不是免费的午餐,因为许多基本的 RDBMS 功能本身都依赖于元组。

数据类型

类型为关系数据库服务于多种目的。像任何其他地方的类型一样,它们建立了一个契约:books.title 是文本,并且始终是文本,recipients.postcode 是一个数字,并且始终是一个数字——后一个假设在收件人居住在英国、加拿大或任何其他几个国家/地区时失效(邮政编码实际上是文本)。

但对 RDBMS 同样重要的是,类型定义了大小,因此决定了页面布局。SQL 标准将整数定义为四个字节,无论特定的四个字节表示未确定的值 (NULL)、零还是 20 亿。声明为固定长度 nCHAR 的文本始终占用 n 个字节,并且较短的值会用空格填充,直到足够长。有时,空格甚至会在 SELECT 中返回,例如在 SQL Server 中。

但是,并非所有类型都定义了固定长度。标准 VARCHAR 类型而是建立了一个最大长度,并且值不会像 CHAR 那样填充。可变长度类型意味着可变长度元组,这反过来又使数据库在将这些元组放入页面时具有更大的灵活性。

总而言之,SQL 标准定义了多种类型,分为数字、布尔值、日期和时间、时间间隔、文本、二进制,一直到 XML 和 JSON。通常,忠实地遵守标准的情况因实现而异。有些人在顶部添加了非标准数据类型,其中货币、几何和地理、范围以及更多内容纷纷出现。

选择数据类型

为数据定义适当的类型通常很简单。如果您正在使用 PostgreSQL,关心精确度,并且值的运行位数达到小数点后八位,那么这将排除浮点类型,并指示您需要的 NUMERIC 的比例。这方面的其他大多数问题也以类似的方式解决。

主要的复杂之处在于文本。ISO 标准可变长度字符类型 VARCHAR 或 SQL Server 中启用 Unicode 的 NVARCHAR 最适合大多数用途,固定大小的 CHAR 仅限于固定长度字符串的特定情况;更难的决定是合适的长度。有时您会很幸运,SMS 的众所周知的外部施加的限制让您得以通过将人们限制为 140 字节的帖子足够长的时间来摆脱困境,他们会习惯它。但即使 Twitter 保持“微”在“微博”中的决心最终也有所放松,与此同时,一个 20 字节的 city_name 字段实际上是在乞求 Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch 的人出现。

空间很便宜,未使用的空间在 VARCHAR 中不算数,并且大多数表甚至不太可能接近每页一个元组。与其为长度字节讨价还价,不如选择一个好的整数,例如下一个数量级(这里是 100)或最长合理值之上大约 2 的幂(255 也是旧数据库软件中的内置限制),这很少值得。

PostgreSQL 是一种特殊情况:除非特别需要长度限制,否则建议使用 CHAR(n)VARCHAR(n)。相反,由 varlena (variable-length array) 数据结构支持的非标准 TEXT 类型提供无界文本存储。

大多数 RDBMS 中的“大对象”类型是一类二进制和文本数据类型,适用于诸如图像和文档之类的值,这些值通常足够长,以至于页面内存储不切实际。从大对象存储读取或写入是读取或写入页面之上的一个额外步骤,因此在线存储在名称、序列号和摘要的规模上仍然是首选。二进制排序统称为大多数 RDBMS 中的 BLOBbinary large object),尽管 Postgres 唯一启用 varlena 的二进制类型是 BYTEA。大型文本类型可能使用 TEXTCLOB,或在 SQL Server 中使用 VARCHAR(MAX)

这里有必要提一下,关系数据库在充当文件服务器方面不是特别擅长。图像、音频或视频文件、文本文档等通常最好放在旨在存储和提供它们的系统中。但是,就数据库而言,这些非结构化数据与特定记录直接相关——例如,为通过制造过程跟踪的机械零件记录的二进制测试输出文件,或正在抓取和分析的网页的源代码——大对象类型就足够了。

半结构化数据类型

在关系数据库的大部分历史中,子字符串等一直是对大对象进行深入研究的限制,否则这些大对象在结构上是不透明的。JSONXML 等数据类型询问的是“如果它们不是这样呢?”。JSON 和 XML 文档都是分层的,早期的数据库设计人员必须分解外键和 JOIN 才能适应。此外,半结构化类型描述了自己的模式。数据库可以施加的唯一要求是值必须格式正确,无论其内容如何。

层次结构并不少见,即使在主要按关系规则运行的信息系统中也是如此。可寻址、可处理的层次结构作为数据类型使存储和检索尽可能简单,并允许良好地融合关系型和文档策略。一个只做包装文档字段的表甚至可以在定义模式的早期阶段成为有用的原型设计工具。虽然 SQL 不太可能与例如 JavaScript 在格式方面的便利性相匹配(以其命名),但围绕这些类型(分层或其他)存在的功能为 数据库编程 开辟了许多可能性。

集合及更多

有些值只有在组合时才有意义:下限和上限、开始日期和结束日期、数字或字符串的有序列表。范围 可以通过分解为例如 started_atended_at 字段来模拟,并且严格来说,在关系术语中,数组 被正确地规范化为具有外键的单独表,但是将范围或数组视为单个值来测试包含、重叠和其他专门操作的便利性是不可低估的。范围类型迄今为止是 PostgreSQL 独有的,Postgres 和 Oracle 都支持数组。

另一种常见情况是,列表示一组定义明确且相对静态的值之一:状态代码、大陆等。CHARVARCHAR 以这种方式占用大量冗余空间,并且必须仔细检查和约束以防止无意义的值进入;INT 代码更安全,但始终必须查找。许多编程语言以枚举或 enum 的形式为数字代码提供可读的名称。PostgreSQL 和 MySQL 都支持将这些作为列数据类型,尽管方式略有不同,MySQL 的操作方式类似于对单个列的约束,而 Postgres 的枚举是可重用的。

构建您自己的

SQL Server 或 Oracle 中的用户定义类型,PostgreSQL 中的复合类型:它们存在。POINT 类型,例如,可以确保其 x 和 y 值是不可分割的,并且在元组内嵌套元组的可能性迅速变得更加复杂。处理自定义数据类型并不总是容易的,尤其是在数据库外部,因此通常首选根据内置或扩展提供的类型定义数据模型。

不仅仅是完整

您感兴趣的每个数据的列表,这是一个整数,那是一个日期等等。类型和元组、列和行使表格在功能上完整。但是,除了完整性之外,还有更多需要考虑的事情:表格如何确保其记录的信息是正确的

正确性和约束 中,我们将介绍关系数据库用于定义和强制执行数据存储所有级别的正确性的工具。

关于作者
Dian Fay

Dian Fay

Dian 并没有真正计划从大学辍学去专攻 SQL 和后端开发,但事情就是这样发生的。十五年后,她设计的数据库支持从工业物流和可追溯性系统到百万级用户社交媒体游戏的一切。她是 MassiveJS 的当前维护者,MassiveJS 是一个用于 Node.js 的开源数据映射器,专注于充分利用 PostgreSQL。