仅仅想到更新数据库模式,就足以让一些开发人员和架构师头痛不已。设计一个好的模式已经很困难了。一旦进入生产阶段,再去更新它?那一定是一场噩梦。对吗?
嗯,从历史上看,这确实是一个噩梦。但它不一定是这样。让我们来看看处理数据库模式的选项,并了解实时模式更新如何解决开发人员在传统关系型数据库和NoSQL文档存储替代方案中遇到的挑战。
传统RDBMS的模式变化挑战
传统的关系型数据库(如MySQL)要求开发人员在模式变化期间锁定表,这实际上是将数据库脱机。一些关系型数据库可以处理一些实时模式变化--例如,Postgres比其他一些数据库提供了更多的实时更新功能--但这种功能仍然相当有限。
在现实中,当你使用这些传统数据库之一时,在不停机的情况下更新模式通常需要创建一个完整的数据副本,这样你就可以并行操作。这允许你在副本中更新模式,然后逐渐将你的应用程序迁移到它上面。
然而,维护一个数据库的多个实例是很昂贵的--即使是对企业来说。仅仅为了防止模式变化期间的停机而使你的数据库成本翻倍,这是一个难以吞咽的药丸。
如果你把你的数据库分片进行横向扩展,那么传统的RDBMS上的模式更新就变得更加复杂。在这种情况下,你已经创建了一个临时的分布式数据库,所以建立它的副本不再是简单的了。分布式关系数据库的复杂性使得它很难在保持数据一致性的同时应用模式变化。
整个过程足够困难,以至于许多开发者都想完全放弃关系型数据库,转而使用 "无模式 "的NoSQL替代方案。
无模式 "的数据库真的无模式吗?
MongoDB等NoSQL数据库提供了一个 "无模式 "数据库引擎。无模式数据库通过改变数据的存储方式来解决模式问题。数据被存储为类似JSON的文件,而不是表和行。
这种方法增加了灵活性,但却牺牲了数据的一致性,因为表或集合中的文档并不要求包含相同的数据字段。在数据库中缺乏一个强制的模式,也会对某些用例的性能产生负面影响。
虽然不必担心数据库模式可能听起来不错,但在大多数情况下,它只是把罐子踢到了路上。因为大多数应用程序要求它们使用的部分或全部数据都是结构化的,所以你最终还是有一个数据模式。它只是在你的应用程序中而不是在你的数据库中被定义和执行。有了NoSQL,寻找一种使模式更新无缝的方法的工程挑战就从数据库技术转移到了你的应用开发团队。
对于某些应用来说,为了NoSQL的灵活性而牺牲传统关系型数据库的模式强制数据一致性是合理的。有时你需要存储一些非结构化的数据!但许多应用--特别是那些有大量事务性工作负载的应用--依赖于数据一致性。
值得庆幸的是,现在有了第三种选择--它结合了关系型数据库和NoSQL数据库的一些最佳元素。
CockroachDB是一个新一代的分布式SQL数据库,它提供了两个世界中最好的东西:一个快速的关系型数据库,具有简单、无停机时间的实时模式更新。它提供的数据库具有传统SQL数据库的一致性和参考完整性,以及NoSQL数据库的可扩展性、灵活性和易用性。
它甚至支持非结构化数据的列与JSONB 数据类型,所以你可以像NoSQL数据库一样存储非结构化数据,但获得传统SQL数据库的数据一致性和熟悉性。
CockroachDB解决了所有与模式变化相关的传统数据库痛点--你不需要处理任何停机时间,也不需要担心复制整个数据库以保持在线。CockroachDB的模式变化在执行时不会对你的应用程序造成任何干扰,也不会对你的用户造成影响。
CockroachDB在渐进状态下处理模式变化,通过确保应用程序只看到旧的模式版本,直到新的模式被完全部署,来保持集群不被表锁。
模式变更是作为一个预定的后台工作运行的,作为一个状态机运行。最重要的是,当模式变化发生时,你的数据库仍然是活跃的,可以为应用程序请求提供服务。
我们有关于如何使用CockroachDB执行在线模式变更的详尽文档,以及最佳实践。但是,让我们通过一个样本数据库和Node.js应用程序来看看在线模式更改是多么的简单
实时数据库模式变更的操作
让我们回顾一下CockroachDB的生产实例中的模式变化。如果你的机器上还没有安装CockroachDB,你可以在几分钟内建立一个免费的CockroachCloud集群。
我们将使用我们在之前的文章中开发的Node.js应用程序。在这里,我们将只看到Node.js从集群中接收的表结构。
在下一节中,我们假设你有一个CockroachDB实例在运行,并且可以使用Node.js应用程序连接到它。
改变Node.js应用程序的数据库模式
我们需要决定在你的Node.js应用中使用哪种数据库驱动。在本教程中,我们将使用PostgreSQL的pg驱动--我们将通过npm下载它,然后启动我们的Express服务器。
我们从一个单列的表开始。我们可以使用CockroachDB SQL shell直接在SQL中进行,也可以使用Node.js pg驱动运行以下查询。在本教程中,我们将使用CockroachDB的SQL语法。
CREATE TABLE IF NOT EXISTS public.person (id INT PRIMARY KEY);
我们的Node.js应用程序将查询这个表,我们将在查询后读取列信息。使用PostgreSQL的pg驱动,我们可以轻松地读取响应字段。让我们写一些JavaScript代码,从我们刚刚创建的person 表中选择所有记录,并读取它作为响应返回的字段(即列)。
let response = await client.query("SELECT * FROM public.person;");
console.log(`[INFO] query reply: \n` + JSON.stringify(response.fields));
res.send(response);
如果我们现在运行这个查询,我们会得到。
"fields":[{"name":"id","tableID":68,"columnID":1,"dataTypeID":20,"dataTypeSize":8,"dataTypeModifier":-1,"format":"text"}]
这个响应显示我们有一个字段,id. 在真实世界的场景中,你的应用程序可能会对每条响应记录使用几十个(如果不是几百个)字段。
现在,让我们通过在CockroachDB的SQL shell中运行一个ALTER TABLE 查询来改变这个表。
ALTER TABLE public.person ADD COLUMN IF NOT EXISTS name STRING;
运行这个查询为我们的表增加了第二列。如果我们在运行时监控我们的数据库(或者事后检查监控数据),我们可以看到它的执行没有任何数据库停机。如果我们有一个应用程序,在这个查询执行时正在数据库上积极地读写,它将能够继续读写而没有任何错误或数据丢失,假设我们应用程序中的代码与模式的 "前 "和 "后 "版本都能工作。
现在,让我们再次从我们的应用程序中查询该表。这一次,我们应该在响应中看到两个字段:id 和name 。
"fields":[{"name":"id","tableID":68,"columnID":1,"dataTypeID":20,"dataTypeSize":8,"dataTypeModifier":-1,"format":"text"},{"name":"name","tableID":68,"columnID":2,"dataTypeID":25,"dataTypeSize":-1,"dataTypeModifier":-1,"format":"text"}]
这就成功了!我们快速而轻松地改变了我们的数据库模式,甚至没有一毫秒的停机时间。
改动主键列
即使我们想改变一个表的主键,CockroachDB的在线模式改变也会起作用!这使得我们的应用更容易过渡到一个新的模式。这使得我们的应用程序更容易从单区域过渡到多区域,而且在许多其他情况下也很有用。如果你对技术细节感兴趣,你可以阅读更多关于如何实现这一目标的文章,但让我们试试,看看它是如何工作的
我们可以在CockroachDB的SQL shell中完成所有这些工作。
首先,我们需要在我们的person 表中创建一个新的列,作为我们新的主键。按照分布式数据库的最佳做法,让我们创建一个UUID列,并设置它为每条添加到表中的记录生成一个随机的唯一ID。
ALTER TABLE person ADD COLUMN uuid UUID NOT NULL DEFAULT gen_random_uuid();
现在,为了改变表的主键,我们可以运行之前添加name 列时的那种命令。
ALTER TABLE person ALTER PRIMARY KEY USING COLUMNS (uuid);
当我们运行该命令时,我们会在SQL shell中得到一个通知。
NOTICE: primary key changes are finalized asynchronously; further schema changes on this table may be restricted until the job completes
这是由于在分布式数据库的多个节点上改变主键的复杂性造成的。然而,从我们作为一个应用程序开发人员的角度来看,这并不是一个问题。在一个大型的生产数据库中,我们可能需要等待一段时间才能进行额外的模式更改,但仍然没有停机,因此对我们的应用没有影响。
我们可以通过在SQL shell中运行下面的命令来确认我们的实时模式变更是否成功。
SHOW CONSTRAINTS FROM person;
这个命令将返回一个表格,列出应用于person 的每个约束条件,我们可以看到,主键现在是我们的uuid 列。架构变更成功了!
一个简单的提醒
当然,每当我们改变数据库模式时,我们可能也需要更新我们的应用程序以反映新的模式。如果我们使用的是NoSQL数据库,情况也是如此,因为 "无模式 "的数据库设置意味着我们需要在应用程序中强制执行数据模式。
通过使用CockroachDB的实时模式更新,我们基本上得到了两方面的好处--我们避免了传统关系型数据库的模式变化所带来的数据库停机和复制问题,而又不必牺牲其提供的数据一致性。
请注意,虽然模式更新在我们的演示数据库中几乎是即时发生的,但在生产应用中,这个命令可能需要更多的时间,因为CockroachDB必须将模式迁移应用到所有节点,而且所有节点必须同意模式的改变。但是不用担心:CockroachDB允许在线改变模式,即使在大规模的生产数据库中也可以实现零停机时间。
接下来的步骤
在这篇文章中,我们讨论了如何在零停机时间内更新CockroachDB集群模式。我们还看了一些其他的数据库选择,包括传统的关系型数据库和NoSQL数据库,并讨论了它们的一些优点和缺点。
在本文结束时,你看到了一个演示,即模式变化的SQL查询是如何实时发生的,以及这些变化如何反映在你的查询中。不过,在你真正在生产中执行任何模式变化之前,你应该回顾一下CockroachDB关于模式变化的文档,了解一些重要的提示和最佳实践。