最小的事务隔离级别



今天,我想带来一个非常有趣的内容,但是对于数据库(DB)的普通凡人程序员部分来说,它经常包含秘密-事务隔离级别。 正如实践所示,许多与IT相关的人员,尤其是与数据库打交道的人,几乎不了解为什么需要这些级别以及如何将其用于自己的利益。

一点理论


事务本身不需要特殊说明;事务是对数据库的N(N≥1)个查询,这些查询将一起成功执行或完全不成功执行。 事务隔离显示了并行事务之间相互影响的程度。
在选择事务级别时,我们试图在事务之间的数据高度一致性和事务速度之间做出选择上达成共识。
值得注意的是,最高的执行速度和最低的一致性是未提交的 。 最低的执行速度和最高的一致性是可序列化的

环境准备


例如,选择了MySQL DBMS。 也可以使用PostgreSQL,但它不支持读取未提交隔离级别, 而是使用 读取已提交级别。 事实证明,不同的DBMS对隔离级别的理解不同。 他们在确保隔离方面可以有各种细微差别,可以有更多的层次,也可以没有知名度。

使用完成的MySQL映像和Docker Hub创建环境。 并填写数据库。

docker-compose.yaml
version: '3.4' services: db: image: mysql:8 environment: - MYSQL_ROOT_PASSWORD=12345 command: --init-file /init.sql volumes: - data:/var/lib/mysql - ./init.sql:/init.sql expose: - "3306" ports: - "3309:3306" volumes: data: 


填充数据库
 create database if not exists bank; use bank; create table if not exists accounts ( id int unsigned auto_increment primary key, login varchar(255) not null, balance bigint default 0 not null, created_at timestamp default now() ) collate=utf8mb4_unicode_ci; insert into accounts (login, balance) values ('petya', 1000); insert into accounts (login, balance) values ('vasya', 2000); insert into accounts (login, balance) values ('mark', 500); 


让我们考虑关卡的工作方式及其功能。
我们将在2个同时执行的事务上执行示例。 有条件的,左窗口中的事务将被称为事务1 (T1),右窗口中的事务将被称为事务2 (T2)。

阅读未提交


数据一致性最差,但事务处理速度最高的级别。 该级别的名称说明一切-每个事务在另一个事务中都会看到未提交的更改( 脏读现象)。 让我们看看这种交易如何相互影响。

步骤1.我们开始2个并行事务。



第2步。我们首先了解一下我们所拥有的信息。



步骤3.现在,我们在T1中执行INSERT,DELETE,UPDATE操作,然后看看另一个事务现在会看到什么。



T2查看来自另一个尚未提交的事务的数据。

步骤4 ,T2可以获得一些数据。



步骤5.当您将更改回滚到T1时,T2接收的数据将是错误的。



在此级别上,不可能根据得出对应用程序重要的结论和关键决策的数据来使用数据,因为这些结论可能与现实相去甚远。
例如,此级别可用于近似计算。 结果COUNT(*)MAX(*)可以在某些非严格报告中使用。
另一个示例是调试模式。 在事务期间,您想查看数据库发生了什么。

阅读已提交


对于此级别,并发执行的事务仅看到来自其他事务的已提交更改。 因此,该水平提供了防止脏读的保护。

步骤1和步骤2与前面的示例相似。

步骤3.我们还对帐户表(T1)执行3个简单的操作,并在两个事务中对这些表进行完整选择。



我们将看到在T2中不存在脏读现象。

步骤4.我们修复T1中的更改​​,并检查T2现在看到的内容。



现在,T2可以看到T1所做的一切。 当我们看到更新和删除的行(UPDATE,DELETE)时,这就是所谓的非重复读取现象;当我们看到添加的记录(INSERT)时,这就是读取幻像的现象。

可重复读


防止非重复阅读现象的水平。 即 我们看不到另一笔交易与另一笔交易中已更改和删除的记录。 但是我们仍然看到来自另一笔交易的插入记录。 阅读幻影不会走开。

再次重复步骤1和步骤2。

步骤3.在T1中,我们执行INSERT,UPDATE和DELETE查询。 之后,在T2中,我们尝试更新在T1中更新的同一行。



我们得到了锁定:T2将等待,直到T1提交更改或回滚。

步骤4.修正T1所做的更改。 并再次从T2的帐户表中读取数据。



如您所见,未观察到非重复阅读幻象阅读的现象。 默认情况下, 可重复读取如何使我们仅阻止非重复读取现象?

实际上,MySQL缺乏为可重复读取级别读取幻像的效果。 在PostgreSQL中,他们也不再使用此级别了。 尽管在此级别的经典表示中,我们必须观察到这种效果。

一个小的抽象示例是生成礼券(代码)及其使用的服务。 例如,攻击者为自己生成了一个证书代码并尝试将其激活,试图连续发送多个请求以激活优惠券。 在这种情况下,我们将使用相同的优惠券开始几个同时执行的交易。 在某些情况下,优惠券可能会发生两次甚至三次激活(用户将获得2倍/ 3倍的奖励)。 使用可重复读取时,在这种情况下,将发生锁定并且激活将发生一次,并且在前两个级别中,可以进行多次激活。 通过使用SELECT FOR UPDATE查询也可以解决类似的问题,该查询也将阻止更新的记录(优惠券)。

可序列化


交易行为的水平,就好像没有其他事物存在时,彼此之间没有影响。 在经典表示中,该级别消除了阅读幻影的影响。

步骤1.开始交易。

第2步 。T2我们读取帐户表,然后T1我们尝试更新T2读取的数据。



我们被锁住了:我们不能更改在另一个事务中读取的事务中的数据。

步骤3. INSERT和DELETE都将我们引向T1中的锁。



在T2完成工作之前,我们将无法使用它读取的数据。 我们获得最大的数据一致性,不会记录额外的数据。 这样做的代价是由于频繁的锁定而导致交易速度变慢,因此,如果应用程序体系结构不佳,这可能会给您带来麻烦。

结论


在大多数应用程序中,隔离级别很少更改,并且使用默认值(例如,在MySQL中,它是可重复的read ,在PostgreSQL中,它是read commit )。

但是周期性地存在一些问题,其中在高数据一致性或事务处理速度之间找到更好的平衡可以帮助解决某些应用程序问题。

Source: https://habr.com/ru/post/zh-CN469415/


All Articles