分享到

简介

数据建模的某些方面会变得主观。事实上,可以说数据建模的大多数方面都是主观的,尽管它有非常数学的基础。任何关于如何表示信息的问题都有许多答案,而且并不总是清楚哪一个最适用于特定上下文。在深入研究实现细节之前,你应该尽可能地理解该上下文和需求。这需要时间,也需要他人的时间,以及大量的问题。研究和访谈对于数据库设计来说,与用户体验设计一样重要:不完整、过于详细、侵入性或结构不良的数据至少会给用户和主题带来痛苦,也会给系统的维护者带来痛苦!即使你被告知执行需求,你也很可能是第一个全面思考问题及其可能后果的人。

数据建模问题的症结在于定义有用的实体,并识别它们在连接网络或图中是否以及如何相互关联。这些实体代表物理对象或无形概念的类别——以及介于两者之间的许多事物,例如位置、货运或分配。但是,占据这个符号空间的表示不受物理定律的约束,并且指示物和被指示物之间不一定存在严格的一对一对应关系。单个实体可能代表你正在处理的许多概念共享的一个方面,甚至是一个完整的子系统;或者,看似单一的概念,分解成多个后可能更有用或更易于管理。

任何实体本身所能表达的意义都有限。祖鲁谚语 “人通过其他人成为人” 对于抽象表示也并非不正确。数据模型很少代表孤立事实的单一类别;它们更常代表系统,即使是少数组件的关系和交互,对于系统的准确和有用表示来说,也与组件本身一样重要。

主观性使得抽象地回答这些问题成为不可能。这是一个需要深入思考和通过积极询问与你收集的数据有利害关系的人来进行调查的练习。即便如此,你最初得出的答案也充其量只是暂时的;极少有数据模型在发布前能保持不变。

什么足够重要可以被视为实体?

“我们真正关心哪些类别?”这是一个显而易见的问题,以至于即使你即将将假设固化下来,也可能忘记提出。无论你使用哪种数据库,定义系统的**主体**和**客体**都是首先要考虑的事情。这也与数据存储的选择有关:例如,如果你关心大量的仪器读数或其他简单事实(你读取的频率远高于更新的频率),这应该引导你选择像 Cassandra 或 HBase 这样的列式数据库。

关系型数据库很复杂(complicated)。在谈论系统时,复杂(complicated)必须与复杂(complex)区分开来。复杂(complex)系统包括神经网络、经济、语言、生命本身:每个组件都基于有限的上下文信息与许多其他组件交互,整个系统与环境交互,历史和行为随之产生。

同时,复杂(complicated)系统只是由许多独立的部分组成,每个部分都像时钟或引擎一样有节奏地行动并被作用。在数据库中编码历史并定义行为是可能的,但这严格来说是一个自上而下的练习;数据模型中任何看似**涌现**的特性,都不过是一个确定性的 bug。数据模型的元素会相互作用,但总是以相同的方式与相同的其他元素作用。数据模型中的某些元素对于数据库表示来说,就像纱门对于潜艇一样,充其量只会增加一个机械故障点。

实体之间的哪些连接很重要?

设计数据模型时,如果多个用户群及其数据存储在相同的表中,有两种方式。我们稍后会回到多租户的更广泛问题,但在共享模式模型中有一个特定的选择,它体现了建模中的一些主要考量:每个记录都应该包含一个 tenant_id 值,还是只包含那些与租户有直接、有意义连接的记录?

从概念上讲,只要强制执行外键约束,识别某一行属于哪个租户并不困难。人们可以沿着关系从一个表 JOIN 到另一个表,回到 tenants 表,然后任何包含你正在寻找的 tenant_id 值的连接产品都属于该租户。然而,随着模式的进一步演变,管理 SQL 可能会变得相当糟糕,并且随着系统中数据量的增加,性能可能会成为一个问题。这并不意味着在更远的表中管理额外的 tenant_id 列就自动值得,但要做出这个合理的决定也不需要太多。

Tenant vs Library

实体和关系构成了一个有向的节点和边图。这里的“有向”意味着边是单向连接。一个带有 tenant_id 值的记录,既暗示了 tenants 表的存在,也暗示了其中特定记录的存在(如果设置了正确的外键约束,则是保证而非暗示!);然而,一个来自 tenants 本身的记录,却不暗示任何其他数据可能属于它。

每一个额外的节点或边都会使图变得更加复杂,这反过来意味着进一步维护和改进数据模型将需要更多的努力。然而,这种复杂性既是一个局部问题,也是一个全局问题。实体集可以以足够重要的关系交织在一起,这对于管理和使用都具有挑战性。这些子系统在一定程度上可以通过 PostgreSQL 中的视图或表继承等机制来屏蔽,其最终效果与面向对象软件开发中的外观模式类似。

组织单元如何交互?

模型的任何元素——无论是实体、关系,还是包含多个实体和关系的子图——都可能代表你正在设计的组织的一部分,或是这些单元之间交互的焦点。库存连接着制造与物流,或物流与销售。费用记录连接着财务和人力资源,以及人力资源与公司各部门。有些参与者可能主要通过少数边缘实体与模型交互;如果他们对系统深层运作了解甚少,这可能会带来挑战,但也可能通过让他们更多地了解和接触其余部分来改善组织的整体运作。

很少有观察结果像康威定律那样经久不衰:组织注定要在其系统设计中复制其沟通结构,数据库也不例外。某些数据建模问题,无论是全部还是部分,实际上是沟通问题,而数据库对此可能帮助甚微。

多少细节?

把前两个问题反过来问也很有用:如果我们完全忽略这个可能的实体或那个关系,数据模型的使用者会错过什么?当然,你最初认为是重要的元素,大多数确实如此。但有些元素可能不像初看起来那么关键,省略或简化它们可以提高模型的整体功能和可维护性。

重要性问题有不止“是”和“否”的答案。你可能不需要将问题空间的某些方面作为独立的实体来表示,但仍然关心像存在或数量这样通用且更易于管理的事实。在制造业中,零件号是关于校准、工艺历史、子组件、容量、公差等大量信息的关键,不同的特性对不同类型的零件很重要。在仓储中,零件号变成了 SKU(“库存单位”),而关键细节要简单得多:有多少、什么颜色、多重、在哪里?

某些信息也很有用,但它们本身已经有内部结构,将其转换为实体和关系并不方便。层级结构、超文本文档、物料清单,甚至数据库中其他地方的实体关系子图的临时“工作副本”;这些或它们包含的信息可以用关系型方式表示,但除非存在跨越外部和内部结构边界的重要关系,否则将其拆开可能不值得。如果此类信息是你的主要关注点,那么专门的数据库可能更适合,例如(用于层级文档的)CouchDB 或 MongoDB。如果它们是其他关系模型的例外情况,那么 JSON 和 XML 等数据类型可以帮助避免将模型拆分到两个或更多数据库中。因为每个额外的数据存储不仅增加了维护和协调的工作量,而且参照完整性——即关系所依赖的信息不会凭空消失的保证——只在单个数据库内部成立。

哪些子图、聚合和统计数据有用?

查询关系型数据库需要时间。具体需要多少时间取决于多种因素:主机电脑的物理特性,如磁盘速度和 CPU 功率;所涉关系型数据库管理系统 (RDBMS) 的操作能力,如缓存和优化;最后是查询本身的构成。你的数据模型必须适应前两类因素所设定的限制,但其成功的主要决定因素之一将是它们如何很好地预见第三类问题。

跨越的关系数量是查询组成中最重要的方面之一,无论是对于性能还是可维护性。高 JOIN 计数不一定意味着不可接受的性能,特别是如果被连接的实体很小或已良好索引,但即使在最好的情况下,每一次连接都会增加查询编写者的认知负担。对于你、后端开发人员和其他数据库用户来说,组装常用结果集应该尽可能简单明了。

数据库提供了一些工具来帮助实现这一点。视图封装了常用的子图及其上的计算,这有助于向用户和应用程序屏蔽复杂性。将你的二阶表示集中在数据库中,还可以确保有一个单一的规范表示,例如包含所有相关信息的采购订单,或仓库的常见统计数据。普通视图对性能没有帮助,因为它们是存储的查询,会被集成到正在运行的语句中。然而,大多数关系型数据库都支持“物化”视图(MySQL 和 MariaDB 是显著的例外)。这些视图将数据持久化到磁盘,像表一样,使得检索速度快得多,但这些数据会变得过时,需要刷新。

这里最后的手段是**反范式化**:将实体的数据或有用的聚合(如计数或总计)作为不同实体的属性直接存储。我们将在未来的章节中更深入地探讨范式,但毋庸置疑,这不是一个可以轻易采取的步骤。尽管如此,如果收集所需数据非常耗时,那么放弃更高层次的范式化可能值得付出额外的管理努力。如果你需要在这里走向极端,是时候再次考虑列式数据库了。

不应该存储什么?

你不必为没有的信息负责。整个行业都围绕数据安全展开,但没有万无一失的安全措施。避免一类非常棘手的问题最简单的方法就是不收集可能给你带来此类问题的数据。请注意,这不仅限于数据库;敏感信息也可能出现在日志、报告、源代码管理和其他系统中。

Redacted files

毫无疑问,最具影响力的单一敏感信息类别与**人**有关。关于人的信息必然具有政治性,而对有人类参与的系统进行建模,既反映也强化了建模者的政治立场。谁进入系统或被输入系统,以何种身份,哪些特征被认为重要或不重要,对谁可见:这一切都是政治。我们才刚刚开始看到随意或侵入性处理个人数据所带来的影响。越来越多的人的信息不断地被灌输到数据模型中,而这些模型往往没有得到相应的精心开发。在不少情况下,它们甚至被明确设计出来以利用这些信息。

个人身份信息(PII)表明其他数据与谁相关。没有 PII,一个随机的医疗记录就是真空中的一段历史。某个地方的某个人,只知道一个数字,接受过这些儿童疫苗接种,在这里出现肺炎,在那里出现轻微皮肤癌,曾经有药物依赖,得过几次流感,还有躁郁症。将该医疗记录号与姓名和地址关联起来,突然你——或任何能看到它的人——就对某个特定人的生活了解得更多,超出了他们可能希望或允许他人了解的范围。对于数据所有者来说,收集超出我们成功保护能力的数据所带来的后果,从短暂的不愉快对话到失业、身份盗窃、勒索和各种仇恨犯罪。

关于医疗记录,许多国家都有严格的访问和披露法律,这是有充分理由的。在大多数其他情况下,信息并没有受到如此严格的法律控制(如果有的话),这意味着有责任弄清楚你需要如何严密地保护那些姓名和地址、出生日期、生物识别信息、母亲的娘家姓,以及护照或纳税人识别号等政府分配的身份值。PII 也可能在你意想不到的地方出现,就像 2004 年 AOL 发布他们自认为匿名的搜索日志给公众时所发生的那样。更糟糕的是,即使是非识别性事实,在数量足够多时也会变得具有识别性:有多少其他人与你拥有相同的居住城市、性别、职称、手机型号和最喜欢的餐厅?这些事实组合还可能与哪些其他事物相关联?

另一种敏感信息允许你代表他人行事。社交媒体或其他网络凭据、银行路由和账户信息、信用卡号都基于信任交予你。在某些情况下,特别是涉及支付时,存在第三方服务可以为你分担这种信任负担。除非你有非常充分的理由亲自承担,否则请使用这些服务。

接下来会发生什么?

数据模型并非一成不变。需求会演变;业务会转型;外部信息源会出现、变化和消失。这不会像应用程序开发那样充满活力,但确实会发生,包括大改动、小调整以及介于两者之间的一切。仔细的设计将有助于保持向更慢、更小变化的方向平衡,但永远无法保证在某个时候不需要对数据模型进行重大“手术”,甚至一些看似微小的变化也可能在部署中带来意想不到的困难。例如,在 PostgreSQL 11 之前,添加一个带有非空默认值的新列需要重写表中的每个记录——对于数百万行来说这可不是什么趣事!

现在比未来更重要。如果你的模型现在没用,你很可能就没有机会看到它以后变得有用。然而,自然或非自然选择的力量总是在起作用,无法适应这些压力的数据库将会消亡。我们稍后将更详细地讨论未来规划的可能性,但即使知道存在可能让你陷入困境的死胡同,也能帮助你避免这种命运。

关于作者
Dian Fay

Dian Fay

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