简介
数据建模的某些方面是主观的。实际上,公平地说,数据建模的大部分方面都是主观的,尽管它具有非常数学的基础。任何关于如何表示信息的问题都有很多答案,而且在特定情境下哪一个最适用并不总是很清楚。在深入了解实现细节之前,你应该尽可能地理解其情境和需求。这需要时间,需要他人的时间,还需要提出大量问题。研究和访谈对于数据库设计来说,与用户体验设计同样重要:不完整、过于详细、侵入性或结构不佳的数据至少会给用户和主题带来与系统维护者一样的痛苦!即使你被告知要执行需求,你也很可能是第一个全面思考问题及其可能后果的人。
数据建模问题的核心是定义有用的实体,并识别它们在连接的网络或图中是否以及如何相互关联。这些实体代表了物理对象或无形概念的类别——以及介于两者之间的不少事物,例如位置、货运或任务。但是,占据这个符号空间的表示不受物理定律的束缚,并且指示符和被指示物之间不一定存在严格的一一对应关系。一个单一的实体可能代表你正在处理的许多概念共享的一个方面,甚至是一个完整的子系统;或者,一个看似单一的概念,如果分解成许多部分,可能会更有用或更易于管理。
任何实体本身能表达的意义都有限。祖鲁谚语“一个人通过他人而成为人”对于抽象表示也并非不实。数据模型很少代表孤立事实的单一类别;它们更多地代表系统,即使是少量组件之间的关系和互动,对于系统准确而有用的表示也与组件本身同样重要。
主观性使得抽象地回答这些问题变得不可能。这是一个需要深思熟虑的练习,也是通过询问与你收集的数据有利害关系的人来积极调查的练习。即便如此,你最初得出的答案也充其量是暂定的;很少有数据模型能在发布前保持不变。
什么足够重要,可以作为实体?
“我们真正关心哪些类别?”这是一个显而易见的问题,以至于即使你即将把你的假设固定下来,它也可能被忽略。无论你使用什么数据库,定义系统的S主体和客体是首先要考虑的事情。它也与数据存储的选择相关:例如,如果你关心大量仪表读数或其他你读入的频率远高于更新的直截了当的事实,那么这应该引导你选择像Cassandra或HBase这样的列式数据库。
关系型数据库很复杂。在谈论系统时,复杂必须与复杂性区分开来。复杂系统包括神经网络、经济、语言、生命本身:每个组件都基于有限的上下文信息与其他许多组件交互,整个系统与其环境交互,历史和行为由此产生。
而复杂的系统,只是由许多独立的部件组成,每个部件都像时钟或发动机一样有节奏地行动和被行动。在数据库中编码历史和定义行为是可能的,但这样做是一个严格的自上而下的练习;数据模型的任何看似“涌现”的特性,都只不过是一个确定性错误。数据模型的元素与其他元素交互,但总是以相同的方式与相同的元素交互。而且,数据库中有些元素的表示就像潜艇上的纱窗一样,充其量只增加了一个机械故障点。
实体之间哪些连接是重要的?
有两种设计数据模型的方法,可以将多个用户群及其数据存储在相同的表中。我们稍后将回到多租户的更广泛问题,但在共享模式模型中有一个特定的选择,它体现了建模中的一些主要考虑点:是否每个记录都应包含一个 tenant_id 值,还是只那些与租户有直接、有意义连接的记录?
从概念上讲,只要强制执行外键约束,识别某行属于哪个租户并不困难。人们可以通过遵循关系,从一个表 JOIN 到另一个表,最终回到 tenants 表,然后任何包含你正在查找的 tenant_id 值的连接产品都属于该租户。然而,随着模式的进一步演变,管理 SQL 可能会变得相当糟糕,并且系统中的数据量足够大时,性能可能会成为一个问题。这并不意味着在更远的表中管理额外的 tenant_id 列就自动是值得的,但让它成为一个合理的决定也不需要太多。
实体和关系构成了一个由节点和边组成的有向图。“有向”在这里意味着边是单向连接。一个带有 tenant_id 值的记录,既暗示了 tenants 表的存在,也暗示了其中一个特定记录的存在(如果设置了适当的外键约束,它不仅仅是暗示,而是保证了!);然而,一个来自 tenants 本身的记录,却不能暗示任何其他数据可能属于它。
每一个额外的节点或边都会使图变得更加复杂,这反过来意味着维护和改进数据模型将需要更多的精力。然而,这种复杂性既是局部问题也是全局问题。实体集可以通过足够重要的关系纠缠在一起,这在管理和使用上都具有挑战性。这些子系统通常可以通过PostgreSQL中的视图或表继承等机制在一定程度上得到保护,其最终效果与面向对象软件开发中的外观模式异曲同工。
组织单元如何互动?
你的模型的任何元素——无论是实体、关系,还是包含多个实体和关系的子图——都可能代表你正在设计的组织的一部分,或者是这些单元之间交互的焦点。库存连接着制造和物流,或者物流和销售。费用记录连接着财务和人力资源,人力资源又连接着公司内的各个部门。有些参与者可能主要通过少数位于边缘的实体与模型交互;如果他们对系统深层运作了解甚少,这可能会带来挑战,但也可能通过让他们更多地了解和接触系统的其他部分,从而改善组织的整体运作。
很少有观察结果能像康威定律那样经得起推敲:组织注定要在其系统设计中再现其沟通结构,数据库也不例外。一些数据建模问题,无论是全部还是部分,实际上都是沟通问题,而数据库对此可能帮助甚微。
需要多少细节?
将前两个问题反过来问也很有用:如果我们完全忽略这个可能的实体或那个关系,数据模型的用户会错过什么?当然,你最初确定的重要元素大部分都是重要的。但有些可能没有初看起来那么关键,省略或简化它们可以改善模型的整体功能和可维护性。
重要性问题有不止“是”和“否”的答案。你可能不需要将问题空间的某个方面表示为一个独立的实体,但仍然关心一些通用的、更易于管理的事实,如存在或数量。在制造业中,零件号是大量信息(如校准、过程历史、子组件、容量、公差等)的关键,不同零件类型有不同的重要特性。在仓储中,零件号变为 SKU(“库存单位”),关键细节要简单得多:有多少、什么颜色、多重、在哪里?
有些信息也很有用,但它本身已经有其内部结构,将其转换为实体和关系并不方便。层次结构、超文本文档、物料清单,甚至是数据库中其他地方的实体关系子图的瞬时“工作副本”;这些或它们包含的信息可以通过关系型方式表示,但除非外部和内部结构之间存在重要的关系边界交叉,否则打破它们通常不值得。如果这类信息是你的主要关注点,那么专门的数据库可能更合适,例如(针对层次结构文档)CouchDB 或 MongoDB。如果它们是其他关系模型的例外,那么 JSON 和 XML 等数据类型可以帮助避免将你的模型拆分为两个或更多个数据库。因为每个额外的数据存储不仅增加了维护和协调的工作量,而且参照完整性,即保证关系所依赖的信息不会消失的承诺,仅在单个数据库内部成立。
哪些子图、聚合和统计数据是有用的?
查询关系型数据库需要时间。具体需要多少时间取决于多种因素:宿主计算机的物理特性,如磁盘速度和 CPU 功率;所用关系型数据库管理系统的操作能力,如缓存和优化;以及查询本身的构成。你的数据模型必须适应前两类因素设定的限制,但它们成功的关键决定因素之一将是它们对第三类问题的预测能力。
所跨越的关系数量是查询组成最重要的方面之一,无论是对于性能还是可维护性。高 JOIN 计数不一定意味着不可接受的性能,尤其是当被连接的实体很小或经过良好索引时,但即使在最佳情况下,每一个 JOIN 都会增加查询作者的认知负担。对于你、后端开发人员和其他数据库用户来说,组装常用结果集应该尽可能简单明了。
数据库提供了一些工具来帮助实现这一点。视图封装了常用的子图和对它们的计算,这有助于向用户和应用程序屏蔽复杂性。将你的二阶表示集中在数据库中,还可以确保有一个单一的规范表示,例如,一个带有所有相关信息的采购订单,或者仓库的常见统计数据。普通视图对性能没有帮助,因为它们是存储的查询,会被集成到正在运行的语句中。然而,大多数关系型数据库支持“物化”视图(MySQL 和 MariaDB 是明显的例外)。这些视图将数据持久化到磁盘,就像表一样,使得检索速度快得多,但这些数据会变得过时,需要刷新。
这里的最后手段是去范式化:将实体的数据或有用的聚合(如计数或总计)作为不同实体的属性内联存储。我们将在未来的章节中更深入地探讨范式,但 suffice 来说,这并非可以轻率采取的步骤。尽管如此,如果收集所需数据过于耗时,放弃更高层级的范式化可能值得额外的管理工作。如果你需要在这里走向极端,那么是时候再次考虑列式数据库了。
什么不应该存储?
你对你没有的信息不负责任。整个行业都围绕数据安全建立,但没有任何安全措施是万无一失的。避免一类非常棘手问题的最简单方法就是不收集可能导致这种问题的数据。请注意,这不仅限于数据库;敏感信息可能出现在日志、报告、源代码控制和其他系统中。
毫无疑问,最敏感且影响最深远的一类信息是关于人的信息。关于人的信息必然具有政治性,而建模一个有人类参与的系统既反映也强化了建模者的政治立场。谁进入系统或被录入系统,以何种身份,哪些特征被认为是重要或不重要的,对谁可见:所有这些都是政治。我们才刚刚开始看到对个人数据随意或侵入性处理的影响。越来越多的人的信息不断被注入数据模型,而这些模型往往没有得到相应的谨慎开发。在不少情况下,它们被明确设计用来利用这些信息。
个人身份信息(PII)表明其他数据与谁相关。没有PII,随机的医疗记录就是真空中的历史。某个地方的某个人,只知其编号,曾接种过这些儿童疫苗,在这里出现过肺炎,那里有轻微的皮肤癌,一度有物质依赖,几次流感,还有躁郁症。将那个医疗记录编号与姓名和地址关联起来,突然间你——或任何能看到它的人——就对某个特定人的生活了解得比他们可能希望或想要他人知道的更多。收集比我们成功保护的更多的数据所带来的后果,对于数据的所有者来说,从短暂的不愉快谈话一直到失业、身份盗窃、勒索以及各种仇恨犯罪。
就医疗记录而言,许多国家都有严格的法律规定访问和披露,这有充分的理由。大多数其他情况下的信息并没有受到如此严格的法律控制,如果有的话,这意味着你有责任弄清楚你需要多严密地保护那些姓名和地址、出生日期、生物特征、母亲的娘家姓,以及政府分配的身份值,如护照或纳税人ID号码。PII也可能出现在你意想不到的地方,就像2004年美国在线发布了他们认为是匿名的搜索日志给公众时发生的那样。更糟糕的是,即使是非识别性事实,如果数量足够,也会变得具有识别性:有多少其他人拥有与你相同的居住城市、性别、职称、手机型号和最喜欢的餐厅?还有哪些其他事物可能与这些事实的组合相关联?
另一种敏感信息允许你代表他人行事。社交媒体或其他网络凭证、银行路由和账户信息、信用卡号都是你受信任获得的。在某些情况下,特别是涉及支付时,存在第三方服务可以为你承担这份信任负担。除非你有非常充分的理由自己承担,否则请使用它们。
接下来会发生什么?
数据模型并非静态的。需求不断演变;业务方向调整;外部信息源出现、变化和消失。虽然这不会像应用程序开发那样快速,但它确实会发生,无论是大的变化,小的调整,还是介于两者之间的一切。仔细的设计将有助于保持变化趋向于更慢、更小的方向,但永远无法保证在某个时候不需要对数据模型进行大手术,即使一些看似微小的变化也可能在部署中带来意想不到的困难。例如,在PostgreSQL 11之前,添加一个带有非空默认值的新列需要重写表中的每个记录——对于数百万行来说,这可不有趣!
现在比未来更重要。如果你的模型现在没有用,你可能就没有机会看到它将来变得有用。自然选择或非自然选择的力量总是在起作用,无法适应这些压力的数据库将会消亡。我们稍后会更详细地讨论未来可能性的规划,但即使知道自己可能会陷入困境,也能帮助你避免这种命运。


