数据库的6个缺点

这里讨论的是关于数据库在概念上存在的问题,并且已经存在了几十年。

1、全局可变状态是有害的
每个程序员很早就学会尽量减少使用全局变量中的状态。全局变量偶尔也有合理的用途,但一般来说,全局变量会导致代码纠结,难以推理。

数据库也是全局可变状态。实际上,它们比全局变量更糟糕,因为交互经常是跨多个系统的,这就更难推理了。另外,数据库是持久的。因此,如果犯了一个错误,导致数据库损坏,那么仅仅修复错误是无法修复损坏的。你必须手动找出损坏的原因并加以修复。在很多情况下,你不可能准确找出损坏的原因,也可能没有足够的信息来完美地修正损坏。在这种情况下,你的最佳选择是恢复到备份或从备份中合并部分数据,但这两种方法都不是最佳选择。

大多数程序员同时认为变量中的全局可变状态是有害的,同时又认为数据库中的全局可变状态是好的,尽管它们存在着大多数相同的问题。

更好的方法是事件溯源 + 物化视图,这一点我们将在本篇博文的后半部分谈到。应用这种模式的方法有很多,重要的是要避免造成其他复杂问题或性能下降。

2、数据模型具有限制性
数据库围绕着一种 "数据模型",如 "键/值"、"文档"、"关系"、"面向列 "或 "图"。数据库的数据模型是其索引数据的方式,而数据库则围绕该数据模型可有效支持的查询类型提供应用程序接口。

没有一种数据模型可以支持所有的使用案例。这就是为什么会有这么多不同的数据库和不同的数据模型存在的主要原因。因此,公司通常会使用多个数据库来处理不同的用例。

有一个更好的抽象概念可以用来指定索引,这也是每个程序员都熟悉的:数据结构。

每个数据模型实际上都是数据结构的特定组合。例如

  • 键/值:map
  • 文档:map的map
  • 关系:map的map,二级索引是附加map
  • 列导向:分类map

数据结构可以像数据模型一样,通过在磁盘上的持久性而变得非常庞大。这包括嵌套数据结构。对持久数据结构的读写操作与对数据模型的相应操作一样高效。如果能用数据结构的简单原始数据来指定索引,那么数据存储就能表达任何数据模型。此外,通过以不同方式组合数据结构,数据存储还可以表达无限多的数据模型。

由于数据库中可能存在的数据模型只占很小一部分(因为每个数据库只实现一种特定的数据模型),因此数据库不能完全满足应用程序需求的情况非常普遍。从头开始构建一个新数据库的成本极高,因此程序员经常会扭曲他们的领域模型,以适应现有的数据库。这就在应用程序的最底层造成了复杂性。如果能通过精确指定 "形状"(数据结构)来塑造数据存储以适应领域模型,这种复杂性就会消失。

以数据结构而非数据模型来指定索引,是我们将在本篇文章后面介绍的后端开发方法的重要组成部分。

3、规范化与非规范化问题
每个使用关系数据库的程序员最终都会遇到归一化(规范化normalization )与去归一化(非规范化denormalization)问题。我们希望尽可能规范化地存储数据,以便有一个明确的真相来源,并消除任何不一致的可能性。但是,存储归一化数据可能会增加执行查询的工作量,因为需要更多的连接。通常情况下,额外的工作量会让你不得不对数据库进行去规范化处理,以提高性能。

如果在处理过程中出现任何错误,多次存储相同信息可能会造成不一致。然而,为了满足性能限制,你不得不以多种方式存储相同的信息,无论是在同一个数据库中还是在多个数据库中。有这个问题的不仅仅是 RDBMS。因此,工程师就有责任确保所有更新数据库的代码在实现一致性时完全容错。这样的代码经常分布在许多服务中。

作为真相源与作为快速回答查询的索引存储之间存在着根本性的矛盾。传统的 RDBMS 架构将这两个概念混淆在同一个数据存储中。

解决方案是将这两个概念分开处理。一个子系统应该用来表示真实源,另一个子系统应该用来在真实源的基础上实现任意数量的索引存储。如果第二个系统能够根据真实源重新计算索引,那么任何引入不一致性的错误都可以得到纠正。

再说一遍,这就是事件源加物化视图。如果这两个系统集成在一起,就不会对性能造成任何影响。更多相关信息,敬请期待。

4、限制性表结构模式
数据库中可存储的值种类繁多。有些数据库只允许使用 "blobs"(字节数组),这就使客户端承担了对域类型进行序列化和反序列化的负担。其他数据库则允许多种类型,如整数、浮点数、字符串、日期等。

很少有数据库能以一流的方式存储域表示,这样查询就能进入域对象内部,获取或聚合嵌套在其中的信息。部分原因是数据库实现语言有别于应用程序语言,因此无法以这些方式进行互操作。有时,您可以扩展数据库来处理语言中立的表示法,例如 Postgres 的扩展,但这种方法很麻烦,而且有局限性。

通常的做法是使用适配器库(如 ORM),将领域表示映射到数据库表示。然而,这种抽象经常会泄露信息并引发问题。我们已经在这里和这里广泛讨论过这个问题,所以我不需要再次讨论 ORM 的所有问题。

被迫以不同于理想领域表示法的方式对数据进行索引纯粹是一种复杂性。至少,你必须编写适配器代码,以便在不同表示法之间进行转换。通常情况下,这些限制会限制可以高效执行的查询类型。数据库模式的限制性迫使您以不可取的方式扭曲应用程序以适应数据库。

这个问题长期以来一直很普遍,因此很难认识到这种复杂性是不必要的。如果您能根据自己的应用(包括所需的域表示)来构建数据存储,这种复杂性就会消失。

5、复杂的部署
数据库不是孤立存在的。一个完整的后端需要使用许多工具:数据库、处理系统、监控工具、调度程序等。大型后端通常需要数十种不同的工具。

更新应用程序可能是一个由迁移、代码更新和基础架构变更组成的复杂协调过程。公司拥有专门负责部署工程的整个团队的情况并不少见。

除此之外,要做好生产准备,还必须确保一切都有足够的遥测数据,以便能够检测和诊断可能出现的任何问题,无论是性能问题还是其他问题。每种工具都有自己的定制机制来收集遥测数据,因此将所有数据汇集到一个监控仪表板是另一项非同小可的工程任务。

部署的复杂性和成本是目前主导软件工程的开发模式的产物,我称之为 "点菜模式"。从表面上看,"点菜模式 "很有吸引力:为架构的每个部分挑选最合适的工具,并让它们协同工作。

点菜模式的现实并不符合这一理想。由于这些工具都是独立设计的,因此 "让它们协同工作 "往往需要大量的工作,包括构建部署的痛苦。而且,正如前面已经讨论过的,这些工具通常远非最佳。固定的数据模型和限制性模式等意味着你经常要扭曲你的应用程序来适应你的工具,而不是塑造你的工具来适应你的应用程序。

如果你退一步思考我们软件工程师的工作,就会发现构建应用程序的高昂成本并不合理。我们所从事的工程领域以抽象、自动化和重复使用为基础。然而,我们需要花费数百或数千人年的时间,才能构建出可以在数小时内详细描述的应用程序--看看几乎所有大型应用程序背后的工程团队规模就知道了。即使是许多小型应用程序,所需的工程工作量似乎也与其功能严重不成比例。抽象化、自动化和重复使用怎么了?为什么构建应用程序所涉及的工程工作不只是该应用程序的独特之处?

点菜模式之所以存在,是因为软件行业在构建端到端应用后端时没有一个统一的模式。如果您使用的工具是在真正的内聚模式下构建的,那么点菜模式的复杂性就会消失,抽象化、自动化和重用的机会就会激增,软件开发的成本也会大幅降低。

6、构建应用程序后端的内聚模型
要超越数据库,找到更好的软件开发方法,就必须从第一原则出发。这是摆脱软件架构数十年惯性桎梏的唯一途径。因此,让我们明确而严格地定义什么是后端,然后再推理后端应如何构建。

后台的主要功能是接收新数据和回答有关数据的问题。回答问题可能需要获取之前记录的数据(例如,"爱丽丝当前的位置是什么?"),而其他问题则可能涉及大量数据的汇总(例如,"过去三个月中,弗里多尼亚人的平均银行账户余额是多少?)回答问题的最一般方法就是在后台收到的所有数据上运行一个函数:
query = function(all data)

暂且不考虑实际情况,您的数据集可能有 10 PB 大小,您的查询需要在几毫秒内得到回复。重要的是,这是一个思考后端设计的起点。与数据库的数据模型不同,这清楚地囊括了所有可能的后端。在满足必要的实际限制(如延迟、可扩展性、一致性、容错性)的同时,您的后端设计越接近这一理想,它就越强大。

你能多接近这一理想?换句话说,实现实用系统所需的最小权衡是什么?

事实证明,你所要做的就是添加一个索引的概念,即一个预先计算好的数据视图,它能让某些查询快速得到解决。于是,上述模型就变成了

indexes = function(data)
query = function(indexes)


每一个已构建的后端都是这个模型的一个实例,尽管没有像这样明确地表述出来。该模型的不同组件通常使用不同的工具:数据、函数(数据)、索引和函数(索引)。

在典型的 RDBMS 后端中,RDBMS 用于数据和索引,而 ElasticSearch 等其他数据库可能用于更多索引。计算(function(data) 和 function(indexes) )通常作为 API 服务器处理程序的一部分完成,或者在使用队列和 Worker 管理的后台作业中完成。

规模较大的后端可能会使用 NoSQL 数据库(如 Cassandra、MongoDB 或 Neo4j)进行索引,使用 Kafka 处理传入数据,使用计算系统(如 Hadoop、Storm 或 Kafka Streams)进行函数(数据)计算。

在所有这些情况下,后端都是由各种专业的工具组成的大汇总。

总之
这些工具都不试图成为针对后端任何组件(数据、函数(数据)、索引、函数(索引))的通用工具,虽然无法满足在所有后端、所有规模、所有性能要求下的需要,但是专业狭隘有其特定上下文,不要试图做全局上帝的工具。