最近,我开始研究数据库在引擎盖下的工作原理,这是这些研究的一小部分的结果。今天,我将分享有关交易的知识,以及数据库管理系统(DBM)如何在数据不一致和一般错误方面最小化错误,这些错误和一般错误可能影响数据库的可用性和性能。我强调每个人都阅读您使用的DBM的文档,以更好地利用其提供的工具并提高应用程序的性能。本文是DBM如何实现这些属性的概括,只有阅读文档,您才能有具体答案。
交易
我们可以说交易是工作单位。有时,很难只使用一个查询执行您想要进行的所有数据操作。甚至可能是不可能的。那是执行一系列查询的交易的地方。
交易通常用于数据操作(这不会阻止或禁止仅阅读查询)。为此,它们具有一组属性,可在数据质量方面使数据操纵和查询更安全。这组特性是由形成缩写词酸的原子,一致性,隔离和耐用性的概念形成的。后来,我们将更深入地研究它们的工作方式和代表。
回到交易中,有多种DBMS以自己的方式解决该主题,在某些领域进行了一些优先级的绩效,而其他人则优先考虑其他操作的绩效。但是总的来说,它们的工作方式非常相似。我们将从启动交易,终止交易及其确切工作的一些命令开始。
交易处理
- 开始
当我们将命令开始到数据库时,我们正在要求它启动一个新的“分支”。在这种情况下,“分支”就像游戏中的检查站一样。我们在这里开始我们的分支,可以执行我们的SQL操纵或查询,并具有一些“特权”,例如无需数据库即可将我们指示的更改倒退的能力。
。- 提交
正如我前面提到的,开始交易,或者,正如我们所说的那样,“分支”为我们提供了一些特权,例如在我们开始该交易的那一刻恢复到数据库状态的能力。但是,我们如何确认这些更改(通过更改,我指的是在同一分支中执行的DML操作)?使用 commit 命令,我们要求数据库保存我们所做的更改。换句话说,我们在交易中执行的所有DML将永久应用于数据库,并且如果没有其他DML命令,则不能将其恢复到先前的状态。
- 回滚
回滚命令告诉数据库,我们不希望应用“在”该交易中执行的任何查询。换句话说,交易中执行的所有操作都将被取消。一些数据库允许在DDL操作(例如创建表)上使用滚动命令,而其他数据库则不使用。这取决于您使用的DBMS。我建议阅读您将在项目中使用的DBM的文档。
- 错误
正如我之前提到的,DBMSS以自己的方式实施交易,错误行为可能会有所不同。但这几乎是数据库执行A 回滚以保持数据可靠性时的标准。这与原子性的概念发生了冲突,我们将看到下一步。
酸
原子能
原子能是决定交易行为的四个概念之一。它主要处理失败案件。原子性的最初想法是,交易是必须成功执行所有查询的工作单位,或者没有执行这些查询。当交易是“打开”的情况下,并且您正在执行查询或操作时,这两者都适用,并且如果存在查询错误,违反约束,出于任何原因,数据库崩溃,甚至是电力中断等外部力量,数据库将会执行回滚。即使在更特别的情况下,例如数据库执行 commit (对磁盘的编写更改)时的崩溃,它也会撤消所有成功的查询,并在交易开始之前返回州。
示例:
让我们在Joã£O和Maria之间进行两个查询:一个从Joã£O的帐户中提取资金,另一个是将其存入Maria的帐户。
从Joã£O的帐户中提取:
UPDATE BANK_ACCOUNTS SET BALANCE - 100 WHERE NAME = 'JOAO'
存入玛丽亚帐户:
UPDATE BANK_ACCOUNTS SET BALANCE + 100 WHERE NAME = 'MARIA'
进行更改:
COMMIT
- [x]Joã£O的查询:数据库中的错误
- []玛丽亚的查询
在我们的示例中,数据库遇到了一些内存错误,无法将两个更改写入磁盘。因此,该数据库没有扣除Joã£O帐户中的金额,而不是将其归功于Maria的帐户(该数据库中的数据不一致),而是在系统中留下100个多拉尔的孔),而是没有写任何更改并恢复了。交易开始之前到州。
隔离
由于大多数数据库管理系统(DBMS)允许来自不同来源的多个连接,每个连接都执行各种查询,因此需要由这些不同的连接执行的交易要隔离,这意味着它们不共享他们的操作和更改,直到它们为坚定的。隔离的原理可确保即使同时发生发生数据库中的交易。
在隔离中,我们称之为读取现象,这证明了在同时发生的不同交易中执行操作时可能发生的错误。总共有四个现象:
肮脏的阅读
当在共享事务状态的隔离系统中执行查询时,就会发生肮脏的读取。发生这种情况时,可能不会进行更改,并且查询将返回不一致的数据。为了说明这一点,让我们使用下表:
Quant | 价格 | |
---|---|---|
1 | 5 | 20 |
2 | 10 | 15 |
我们在交易1:
中启动以下查询
select sku, quant*price from orders
结果如下:
QuantXprice | |
---|---|
1 | 100 |
2 | 150 |
接下来,事务2从另一个数据库连接开始,并执行以下操作:
update orders set quant+5 where sku = 1
orders
表现在看起来像这样:
Quant | 价格 | |
---|---|---|
1 | 10 | 20 |
2 | 10 | 15 |
交易1然后执行另一个查询:
select sum(quant*price) from orders
查询结果将是:
sum(QuantxPrice) |
---|
350 |
然后交易2执行回滚命令,取消其所做的所有更改,而orders
表返回其初始状态。因此,sum(quant X price)
操作的结果不是350,而是250。
您的示例非常有效地说明了“肮脏读取”的概念,以及当不隔离事务时,数据库系统中的数据不一致。当一个交易可以看到已通过另一笔交易修改的数据,但其他事务尚未进行更改时,就会发生肮脏的读取。
。不可重复阅读
不可重复的读取与肮脏的读数非常相似,其区别在于事务2在交易1执行第二个查询之前进行了更改。这导致数据不一致。您可能会想:“但是,如果更改已经实施,我的报告是否应该拥有最新的数字?”但是,这与隔离原则相矛盾,该原则旨在隔离每笔交易的状态和变化。
在数据库经历大量变化的大型系统中,这可能是有问题的。由于交易可以观察其他交易的变化,因此结果可能是不一致的或出乎意料的。
要说明,让我们从肮脏的读物中使用同一表:
Quant | 价格 | |
---|---|---|
1 | 5 | 20 |
2 | 10 | 15 |
事务1执行查询:
select sku, quant*price from orders
结果如下:
QuantXprice | |
---|---|
1 | 100 |
2 | 150 |
然后,事务2在另一个数据库连接中开始并执行以下操作,然后进行提交:
update orders set quant+5 where sku = 1 commit
orders
表将永久看起来像这样:
Quant | 价格 | |
---|---|---|
1 | 10 | 20 |
2 | 10 | 15 |
交易1然后执行另一个查询:
select sum(quant*price) from orders
查询结果将是:
sum(QuantxPrice) |
---|
350 |
这个示例非常相似,因为两种情况都有相似之处。关键区别在于,肮脏的读取不需要进行更改以使其他交易观察结果,而在不可重复的读取中,必须进行更改以进行交易以观察它。
>幻影阅读
幻影读物与其他两个现象相同,这是数据不一致。不同之处在于,幻影读取涉及最初未读取的行。 Phantom读取没有对集合或单行进行更改,而是增加了在初始读取中未读取但影响第二个结果的行。它称为幻影读取,因为有一些行会改变结果,但没有读取,因为变化是在交易初始查询之后发生的。
让我们用同一表示例说明:
Quant | 价格 | |
---|---|---|
1 | 5 | 20 |
2 | 10 | 15 |
事务1执行以下查询:
select sku, quant*price from orders
结果如下:
QuantXprice | |
---|---|
1 | 100 |
2 | 150 |
然后,事务2在另一个数据库连接中开始并执行以下操作,然后进行提交:
insert into orders values(3, 15, 2) commit
现在的表看起来像这样:
Quant | 价格 | |
---|---|---|
1 | 5 | 20 |
2 | 10 | 15 |
3 | 15 | 2 |
现在,事务1执行另一个查询:
select sum(quant*price) from orders`
查询结果将是:
sum(QuantxPrice) |
---|
280 |
在这种情况下,在单独的交易中添加了带有SKU 3的行,但影响了交易1的查询的一致性,并添加了“ Phantom Row”。
丢失的更新
丢失的更新并不是完全阅读的现象,但是我们将它们包括在此主题中。当两项交易试图对同一行进行更改时,就会发生丢失的更新,其中一个最终覆盖了另一个行的更改。在这种情况下,更改不是在交易之间共享的,并且由于交易没有共享状态(他们读/更改,完成等),发生写入冲突的情况发生,其中一项交易“放弃”其自身的更改另一笔交易进行。
让我们用上一个示例中使用的相同表进行说明:
Quant | 价格 | |
---|---|---|
1 | 5 | 20 |
2 | 10 | 15 |
事务1执行以下查询:
update orders set quant+5 where sku = 1
表看起来像这样(重要的是要注意没有发生任何提交):
Quant | 价格 | |
---|---|---|
1 | 10 | 20 |
2 | 10 | 15 |
之后,交易2开始,它也执行修改查询并提交:
update orders set quant+10 where sku = 1 commit
现在,该表已永久更新如下:
Quant | 价格 | |
---|---|---|
1 | 15 | 20 |
2 | 10 | 15 |
之后,交易1执行查询:
select sku, quant*price from orders
预期的结果是:
QuantXprice | |
---|---|
1 | 200 |
2 | 150 |
但是,由于更新丢失,交易1依赖“委托表”(交易2刚刚进行了更改),结果将是:
QuantXprice | |
---|---|
1 | 300 |
2 | 150 |
这意味着未经明确命令丢弃了交易1的不承诺的更改,从而导致意外结果和数据不一致。
这些是数据库隔离旨在消除的现象。那么,隔离水平到底是什么?
隔离水平
隔离水平分为不同的水平,因为每个隔离级别旨在解决特定问题(读取现象)。每个DBM都根据问题并要求其寻求解决的问题实现其隔离模型(在某些DBMS中,您可以为交易选择隔离级别)。隔离水平包括:
-
读取不合格:无隔离;无论是否承诺,都可以从交易外部观察到任何变化。
-
读取所承诺的:最小隔离;在交易中进行的任何更改都可以通过另一笔交易观察到。
-
可重复的读取:中等隔离;交易确保当查询读取一行时,该行在整个交易的执行过程中保持不变。
-
快照:真隔离。事务中的每个查询都是根据数据库中在开始交易之前在数据库中所做的元素执行的。这就像采用数据库当前状态的“快照”并根据其执行查询,完全隔离了该交易与其他任何交易可能会产生的任何其他更改。
-
序列化:在可序列隔离水平上,DBMS定义了交易彼此分离的程度,从而确保在多次交易的同时执行过程中数据一致性。换句话说,可序列化的隔离确保即使同时进行了多次交易,最终结果也是相同的,就像它们是依次执行的,一个接一个地执行。
下表说明了每个隔离级别的问题。
隔离级 | 肮脏的阅读 | 不可重复阅读 | 幻影阅读 | 丢失更新 |
---|---|---|---|---|
阅读未投入的 | 可能 | 可能 | 可能 | 可能 |
阅读委托 | 防止 | 可能 | 可能 | 可能 |
可重复阅读 | 防止 | 防止 | 可能 | 可能 |
快照 | 防止 | 防止 | 防止 | 防止 |
序列化 | 防止 | 防止 | 防止 | 防止 |
这些隔离级别提供了数据一致性和性能之间的一系列权衡,使您可以选择最适合应用程序要求的级别。
一致性
在整个文本中,我提到了“数据不一致”一词,这基本上是当数据执行操作命令后应该假设其应值的值时,最终它与现实世界中的事件不匹配。在这种情况下,我们将解决数据库中的数据不一致。
为了说明这一点,让我们使用两个示例表:
表“订单”:
Quant | 价格 | |
---|---|---|
1 | 2 | 3 |
2 | 5 | 2 |
表“ relat_orders”:
金额 | |
---|---|
1 | 5 |
2 | 10 |
在此示例中,我们有一个关系不一致。 “ relat_orders”表具有“量”列,这是“订单”表中Quant X价格的结果。 SKU 1的“数量”应为6,但实际上有5个,因为桌子没有适当的关系。为了促进数据库中的表建模,DBMS具有约束,这些规则适用于列,以标准化行为或之间的关系。约束的示例包括唯一的,而不是null和外国密钥约束。
胃酸一致性的概念是指维持所有约束完整性的数据库。术语“完整性”是指数据库中数据状态的准确性或正确性,在该数据库中,数据库无法从一致的状态过渡到不一致的状态。让我们使用一些脚本来更好地理解这个概念。
表创建脚本(PostgreSQL):
CREATE TABLE orders (id serial,
sku int,
status varchar(10) NOT NULL
);
元组插入脚本(PostgreSQL):
INSERT INTO orders (sku) VALUES (1);
数据库无法接受第二个脚本,因为“状态”列必须在所有插入的元组中具有一个值(由于“非null”约束)。如果发生插入,数据库将从一致的状态过渡到不一致的状态,因为它违反了约束,因此违反了一致性的原则。
耐用性
这是这组属性中的最后一个属性,使数据库在整个创建,管理和数据存储过程中如此安全。耐久性可确保交易中所做的所有更改都写在提交后的非易失性内存中,并且不能以任何其他方式更改,除非通过后续交易。换句话说,耐用性确保数据存储并可以在停电,错误或任何类型的数据库故障的情况下进行查询和操纵,从而导致其在一定时期内无效。
耐用性的权衡很明显。为了建立耐久性属性,必须将数据写入非挥发性内存(通常是硬盘驱动器),并且非挥发性内存自然很慢(我将写有关数据库如何存储磁盘上的数据以及为什么它们在将来某些操作)。
使用RAM(挥发性记忆)的Redis等内存数据库,以对酸性属性的益处进行权衡,从而在写作和阅读操作方面取得了支持性能的好处。在耐用性方面,权衡也很明显。继续使用REDIS,对数据的低延迟访问会带来不可预测的主要力量(停电,错误等)关闭服务的风险,从而导致数据丢失,因为它不能保存在非挥发性内存中。 Redis还以自己的方式实现了一些类似于原子,一致性,孤立性和耐用性。它符合非常具体的要求,令人惊讶的是,这些需求与使用挥发性内存的数据库完全不同。
结论
这样,我们已经能够理解具有酸性属性的数据库如何最大程度地减少读写操作的错误。重要的是要了解您在项目中使用的DBM如何实现这些“概念”。我在这里介绍的是数据库如何接近交易管理和数据存储的概括。
最后,我建议在Udemy上提供的“数据库工程的基础”课程(我不是一个通常为课程付费的人,但这确实值得)。我将在下面留下链接。
课程:Fundamentals of Database Engineering
非常感谢阅读此书的每个人!