数据库事务处理,就像你五岁一样
这是什么?
我从事软件开发多年,也教过一段时间的网页开发课程。课程的一部分内容是关于 MySQL 的,但每次这部分结束后,我总觉得还有很多东西没有讲完。
为了帮助学生巩固数据库知识,我把一些心得体会记录了下来,现在我打算把它们整理到这里,希望能对更多人有所帮助。
什么是数据库事务?
现在流行用“听众只有5岁”这样的方式来解释事情,所以我也要这么做。
一个5岁的孩子拥有两个银行账户,这没错,但他终究还是个5岁的孩子。
假设你正在使用银行应用程序,想要将 100 美元从一个账户转到另一个账户——为了方便说明,我们假设是从储蓄账户转到支票账户。根据你可能已经了解的数据库知识,你或许已经猜到,这笔钱实际上并没有“消失”。转账过程分两步完成:
1)你的储蓄账户余额减少了100美元
2)您的支票账户余额增加了100美元
很简单。
现在,假设在步骤 1 和步骤 2 之间的那一瞬间出了问题。比如银行数据中心断电了之类的。这样一来,你的储蓄账户里少了 100 美元,而支票账户里原本应该有的 100 美元也没了,对吧?
幸好并非如此。如果真是那样,你会看到很多诸如“银行停电导致数百万人周末没钱喝酒”之类的新闻标题,而且我肯定会记得读到过。那么,是什么避免了这类问题的发生呢?
如果你认为这次转账可能会出现我上面描述的问题,你会选择哪种方案?
1)那就去做吧,如果我亏了100美元,那就亏100美元。2
)那就去做吧,如果出了什么问题,就把这100美元存回我的储蓄账户,以后再试。
你会选择#2,对吧?至少,你希望如果传输没有完全成功,你可以回到起点再试一次。
数据库就是这样做的:它们使用一种叫做事务(transaction)的技术。根据 Techopedia 的解释,事务是……
“a logical unit that is independently executed for data retrieval or updates”
对一个五岁孩子来说,这简直是个糟糕的定义。算了,别管它了。
数据库事务就是一系列必须全部完成或全部不完成的步骤。就像在两个银行账户之间转账一样,如果有些操作无法完成,最好将所有操作回滚到初始状态。
在数据库中,事务不仅仅涉及金钱,而且无时无刻不在发生。假设您在数据库中更改了某个客户的 ID 号,然后需要更新该客户的所有订单以反映新的 ID。这可能涉及数百个步骤(更改客户 ID 需要一步,而更新该客户的订单则需要无数步)。如果在此过程中发生任何意外情况——不仅仅是断电,也可能是像某个订单记录被其他用户使用这样简单的问题——最好的做法是获取错误信息,解释发生了什么,然后将所有内容(客户和订单)恢复到原来的位置,再重试。您最不希望看到的情况是,客户记录和大约 30 条订单记录都被更改了,而几十条订单记录却保持不变。
(此处插入关于酸性物质的笑话)
这就引出了酸的话题。
ACID 是数据库领域创造的一个缩写词,用来描述事务的本质(或者说,一段软件必须满足哪些条件才能被认定为数据库)。
我们不妨反着来,因为我觉得这样更容易理解(而且 DICA 估计早就被委员会否决了)。
(持久性) ——一旦交易完成(或者我们称之为“提交”),就无法撤销或以任何方式作废。因此,一旦客户记录及其订单完全更新,即使重启整个数据中心,客户和订单信息仍然会保持不变。
注意:这听起来可能有点傻,因为它很简单。但实际上,很多数据库的大部分工作都在计算机内存中进行,只有偶尔才会将数据写入磁盘。这意味着,除非数据库非常谨慎,否则数据可能会被更改而无法保存。
你可能还会想:“如果我把钱从储蓄账户转到支票账户,然后再从支票账户转回储蓄账户,这不就相当于撤销了之前的操作吗?” 不对。某种程度上来说,两者确实相同,因为最终结果和开始时一样。但另一方面,这就好比说,如果你恰好在同一条跑道上起飞和降落,那么去法国度假就不算数了。把钱转到支票账户再转回来,实际上是两笔交易。现在就去试试,然后查看你的银行账单。你会发现根本没有“撤销”这回事:你只是像世界上最小的对冲基金交易员一样,把钱挪来挪去而已。
(我)是独立的——我的交易与你的交易没有任何关系。如果我在ATM机上从一个账户转账100美元到另一个账户,而你正在银行办理抵押贷款手续,这与我没有任何关系。银行能够同时处理大量交易而不会相互干扰,数据库也能做到这一点。
(一致性) ——一旦交易完成,任何查看数据库的人都应该看到相同的信息(当然,前提是他们拥有访问权限)。如果我将100美元从储蓄账户转到支票账户,我应该能够查看我的余额并看到资金已转移。事实上,即使我通过某种方式在世界另一端的ATM机上查看我的账户,我也应该看到资金已转移。数据库只有在所有人都能看到交易完成后才会记录该交易已完成。
--- 书呆子预警 ---
这就是 SQL 数据库和 NoSQL 数据库之间的一个主要区别。SQL 数据库(例如 MySQL、Microsoft SQL Server 和 Oracle)倾向于追求即时一致性,就像我上面描述的那样。这是它们的特点。而像 MongoDB 这样的 NoSQL 数据库则追求最终一致性。最终一致性就好比说,如果你转账 100 美元,你可以在手机应用上看到,也可能在你当地的银行看到,但城里另一家银行或者世界另一端的银行可能需要几秒钟才能看到。这并非糟糕的权衡——大多数人可能并不在意世界另一端的人是否能立即看到这笔转账——但了解你使用的是哪种一致性类型非常重要。
最终一致性不足以满足 ACID 的要求。听到这话,许多 NoSQL 数据库社区成员决定他们本来就不想追求ACID,而是选择以下特性:
1) 基本可用 (Basic Available) - 通常情况下可以正常运行;
2) 软状态 (Soft-state) - 更新并非总是立即生效,但;
3) 最终一致性 (Inventional Consistent) - 更新最终会生效。
又称 BASE(明白了吗?)
MongoDB 的最新版本似乎已经实现了近乎即时的一致性,因此 NoSQL 世界可能很快就会发生一些变化。
--- 书呆子警报结束 ---
为什么最终一致性被认为是一个好主意?因为像 MongoDB 这样的 NoSQL 数据库在分片等技术上投入巨大,分片允许你将数据库分布在许多不同的服务器上,而不是将其放在一个大型服务器上。分片的优势之一是,你可以向 MongoDB 配置中添加服务器——无论是旧的、新的、便宜的还是强大的——并让新服务器承担一部分工作。它还使得在世界各地部署数据库变得更加容易,这些数据库可以像一个大型数据库一样协同工作。这非常非常棒,但有时这意味着事务需要经过多次传递才能被数据库的所有部分感知到。最终一致性的支持者认为,最好提前承认事务可能不会立即对所有地方的每个人都可见(因为正如我提到的,这通常也不是人们所期望的)。
原子性——这就是之前提到的“要么全做,要么全不做”的概念。它意味着事务中的所有步骤都必须执行,否则一个都不做。就像原子是最小的物质单元(或者说曾经是),事务是数据库应该执行的最小工作单元。如果数据库无法执行事务中的所有步骤,那就把所有数据都放回原处,然后离开(打个比方)。
那么数据库是如何完成这一切的呢?我们回到转账的例子。在这种情况下,数据库会将信息写入一个名为事务日志的特殊文件中。它就像数据库的待办事项清单。当数据库被告知要将储蓄账户余额减少 100 美元,并将支票账户余额增加 100 美元时,它会将这两个步骤都写入事务日志。当执行步骤 1(将储蓄账户余额减少 100 美元)时,它会在步骤 1 旁边打上一个相当于数据库标记的勾号。当执行步骤 2 时,它会在步骤 2 旁边再打上一个勾号。然后,它会注意到所有操作都已完成,并将这两个步骤划掉,以告知自己整个事务已完成。
如果发生诸如断电之类的突发情况,数据库知道重启后首先要做的事情就是查看事务日志,看看是否有任何未被完全划掉的任务。这些都是未提交的事务,当系统发现此类事务时,它会检查执行步骤,看看哪些步骤已经完成(例如,将储蓄减少 100 美元)。为了纠正错误,数据库会撤销这些步骤(或者我们称之为回滚)。
好了,今天就到这里吧。请给我一些反馈,特别是如果你想看到我讲解其他类似内容的话!
文章来源:https://dev.to/johndougherty68/database-transactions-like-youre- Five-50i4