关系型数据库的数据隔离级别Read Committed与Repeatable Read

2023-12-04 14:12

本文主要是介绍关系型数据库的数据隔离级别Read Committed与Repeatable Read,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、背景

数据库隔离级别会影响到我们的查询,本文试图以生产中的示例,给你一个直观的认识。

所谓,理论要结合实践,才能让我们理解得更加透彻。

另外,隔离级别的知识面很大,本文也不可能俱全,下面给出本文要阐述的目标:

二、目标

  • Read Committed与Repeatable Read的区别
  • 实际编程中的使用场景
  • 什么是Phamtom
  • 什么是Write Skew

三、隔离级别

1、可重复读

可以重复读的意思是,在同一个事务中,多次读取同一条记录,返回的数据是相同的;不会因为其他的事务修改它而变化。

必须要先理解这个默认隔离级别,才好反过来理解读已提交的级别。

最后,在你实际的应用中,要决定是否降低隔离级别。(以我实际的使用经验看,降低到读已提交级别,不仅符合程序的要求,而且提升了数据库的性能)
我们都知道,隔离级别越低,数据库的读写性能就越高,所谓“鱼与熊掌不可兼得”~~

下面摘引一个示例:

-- 事务A查询主键ID=7的姓名,返回是Aaron
session1> BEGIN;
session1> SELECT firstname FROM names WHERE id = 7;
Aaron-- 事务B查询主键ID=7的姓名,返回是Aaron
-- 随后,把它修改为Bob,最后提交事务
session2> BEGIN;
session2> SELECT firstname FROM names WHERE id = 7;
Aaron
session2> UPDATE names SET firstname = 'Bob' WHERE id = 7;
session2> SELECT firstname FROM names WHERE id = 7;
Bob
session2> COMMIT;-- 事务A二次查询主键ID=7的姓名,返回仍然是Aaron
session1> SELECT firstname FROM names WHERE id = 7;
Aaron
session1> COMMIT;

上述的过程与结论好理解,在实际编程中,你一定要考虑,现实场景会是需要如此吗?

如果更新换成账户余额、库存和积分等变化频繁的场景呢。。。

把问题先留在这里,我们继续另外一个问题:

  • 因为 Phamtom 而导致的 Write Skew现象

再举一个示例:
在这里插入图片描述
重复演示一遍,什么是可重复读的隔离级别。。

那么怎么会导致幻影Phamtom呢?

所谓幻影是指第一次查询的时候没有它,而更新的时候,却把它更新成功了。

在这里插入图片描述
事务A,期望是更新Alice/Bob/Carol这三个人的积分+1
当事务B,新增了一个人Frank,其积分也大于740。
结果,事务A按更新条件(score > 740)执行,导致不在期望中的Frank也被更改了。

这就是Write Skew。
换句话说,可重复读,虽然可以避免查询的可重复,但是它解决不了幻影带来的写倾斜Write Skew。

  • 怎么解决幻影现象
    批量更新记录,更新条件是上一步查询出来的记录ID集合。
select * from gamer where score >= 740;
# 100
# 101
# 102
# 103
update gamer a set a.credit = credit + 1 where a.id in (100,101,102,103);-- 不要使用区间比较
update gamer a set a.credit = credit + 1 where a.score >= 740;

2、读已提交

和可重复读相比,它会读取到其他事务提交的记录。

这里,以第一个示例进行修改举例。

-- 事务A查询主键ID=7的姓名,返回是Aaron
session1> BEGIN;
session1> SELECT firstname FROM names WHERE id = 7;
Aaron-- 事务B查询主键ID=7的姓名,返回是Aaron
-- 随后,把它修改为Bob,最后提交事务
session2> BEGIN;
session2> SELECT firstname FROM names WHERE id = 7;
Aaron
session2> UPDATE names SET firstname = 'Bob' WHERE id = 7;
session2> SELECT firstname FROM names WHERE id = 7;
Bob
session2> COMMIT;-- 事务A二次查询主键ID=7的姓名,返回变成了Bob
session1> SELECT firstname FROM names WHERE id = 7;
Bob
session1> COMMIT;

同一个事务的两次相同查询,返回的记录会因为其他事务的提交而变化。

那么它的适用场景是什么呢?

  • sql乐观锁更新的自旋

sql乐观锁更新,不外乎以下两种写法:

-- version机制, 先查询出version, 每次更新记录的时候,version值自动加1
select version as old_version from gamer where id = 101;
# 1
int new_score = 1000;
update gamer a set a.score = {new_score}, a.version = a.verson + 1 
where id = 101 and version={old_version}
-- 实际执行语句:
update gamer a set a.score = 1000, a.version = a.verson + 1 
where id = 101 and version=1;-- 更新条件, 将查询出来的score作为查询条件,保证在更新的时候,被更新的值是前面查询出来的值。
select score as old_score from gamer where id = 101;
# 980int new_score = 1000;
update gamer a set a.score={new_score} where id = 101 and score={old_score};
-- 实际执行语句:
update gamer a set a.score=1000 where id = 101 and score=980;

这里有一个问题,如果更新的并发度比较大的时候,会出现频繁的更新失败。
所以,建议你在更新的外围,使用一个自旋。

下面是一个java语言实现的伪代码:

// 自旋3次
boolean flag = false;
int newScore = 1000;
for (int i = 0; i < 3; i++)
{// 每次自旋,都再次查询数据库,因为其他事务可能修改该记录。所以隔离级别必须是读已提交。// 如果是可重复读,这里的自旋就没有任何意义。Game game = gameService.findById(101);falg = gameService.update(game.getScore(), game.getId(), newScore);//或者 falg = gameService.update(game.getScore(), game.getId());// 更新成功,提前退出if (flag) {break;}
}

四、查询和设置事务隔离级别

show variables like 'transaction_isolation';
show variables like 'tx_isolation';

在这里插入图片描述

-- 设置全局事务级别为 READ-COMMITTED
set global transaction isolation level read committed;

阿里云的RDS,支持修改该参数,不过需要重启生效。
在这里插入图片描述

这篇关于关系型数据库的数据隔离级别Read Committed与Repeatable Read的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/453755

相关文章

MySQL快速复制一张表的四种核心方法(包括表结构和数据)

《MySQL快速复制一张表的四种核心方法(包括表结构和数据)》本文详细介绍了四种复制MySQL表(结构+数据)的方法,并对每种方法进行了对比分析,适用于不同场景和数据量的复制需求,特别是针对超大表(1... 目录一、mysql 复制表(结构+数据)的 4 种核心方法(面试结构化回答)方法 1:CREATE

JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)

《JavaWeb项目创建、部署、连接数据库保姆级教程(tomcat)》:本文主要介绍如何在IntelliJIDEA2020.1中创建和部署一个JavaWeb项目,包括创建项目、配置Tomcat服务... 目录简介:一、创建项目二、tomcat部署1、将tomcat解压在一个自己找得到路径2、在idea中添加

详解C++ 存储二进制数据容器的几种方法

《详解C++存储二进制数据容器的几种方法》本文主要介绍了详解C++存储二进制数据容器,包括std::vector、std::array、std::string、std::bitset和std::ve... 目录1.std::vector<uint8_t>(最常用)特点:适用场景:示例:2.std::arra

Java中自旋锁与CAS机制的深层关系与区别

《Java中自旋锁与CAS机制的深层关系与区别》CAS算法即比较并替换,是一种实现并发编程时常用到的算法,Java并发包中的很多类都使用了CAS算法,:本文主要介绍Java中自旋锁与CAS机制深层... 目录1. 引言2. 比较并交换 (Compare-and-Swap, CAS) 核心原理2.1 CAS

MySQL MHA集群详解(数据库高可用)

《MySQLMHA集群详解(数据库高可用)》MHA(MasterHighAvailability)是开源MySQL高可用管理工具,用于自动故障检测与转移,支持异步或半同步复制的MySQL主从架构,本... 目录mysql 高可用方案:MHA 详解与实战1. MHA 简介2. MHA 的组件组成(1)MHA

MySQL中的DELETE删除数据及注意事项

《MySQL中的DELETE删除数据及注意事项》MySQL的DELETE语句是数据库操作中不可或缺的一部分,通过合理使用索引、批量删除、避免全表删除、使用TRUNCATE、使用ORDERBY和LIMI... 目录1. 基本语法单表删除2. 高级用法使用子查询删除删除多表3. 性能优化策略使用索引批量删除避免

MySQL 数据库进阶之SQL 数据操作与子查询操作大全

《MySQL数据库进阶之SQL数据操作与子查询操作大全》本文详细介绍了SQL中的子查询、数据添加(INSERT)、数据修改(UPDATE)和数据删除(DELETE、TRUNCATE、DROP)操作... 目录一、子查询:嵌套在查询中的查询1.1 子查询的基本语法1.2 子查询的实战示例二、数据添加:INSE

Linux服务器数据盘移除并重新挂载的全过程

《Linux服务器数据盘移除并重新挂载的全过程》:本文主要介绍在Linux服务器上移除并重新挂载数据盘的整个过程,分为三大步:卸载文件系统、分离磁盘和重新挂载,每一步都有详细的步骤和注意事项,确保... 目录引言第一步:卸载文件系统第二步:分离磁盘第三步:重新挂载引言在 linux 服务器上移除并重新挂p

使用MyBatis TypeHandler实现数据加密与解密的具体方案

《使用MyBatisTypeHandler实现数据加密与解密的具体方案》在我们日常的开发工作中,经常会遇到一些敏感数据需要存储,比如用户的手机号、身份证号、银行卡号等,为了保障数据安全,我们通常会对... 目录1. 核心概念:什么是 TypeHandler?2. 实战场景3. 代码实现步骤步骤 1:定义 E

使用C#导出Excel数据并保存多种格式的完整示例

《使用C#导出Excel数据并保存多种格式的完整示例》在现代企业信息化管理中,Excel已经成为最常用的数据存储和分析工具,从员工信息表、销售数据报表到财务分析表,几乎所有部门都离不开Excel,本文... 目录引言1. 安装 Spire.XLS2. 创建工作簿和填充数据3. 保存为不同格式4. 效果展示5