关系型数据库的数据隔离级别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

相关文章

python处理带有时区的日期和时间数据

《python处理带有时区的日期和时间数据》这篇文章主要为大家详细介绍了如何在Python中使用pytz库处理时区信息,包括获取当前UTC时间,转换为特定时区等,有需要的小伙伴可以参考一下... 目录时区基本信息python datetime使用timezonepandas处理时区数据知识延展时区基本信息

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Pandas统计每行数据中的空值的方法示例

《Pandas统计每行数据中的空值的方法示例》处理缺失数据(NaN值)是一个非常常见的问题,本文主要介绍了Pandas统计每行数据中的空值的方法示例,具有一定的参考价值,感兴趣的可以了解一下... 目录什么是空值?为什么要统计空值?准备工作创建示例数据统计每行空值数量进一步分析www.chinasem.cn处

如何使用 Python 读取 Excel 数据

《如何使用Python读取Excel数据》:本文主要介绍使用Python读取Excel数据的详细教程,通过pandas和openpyxl,你可以轻松读取Excel文件,并进行各种数据处理操... 目录使用 python 读取 Excel 数据的详细教程1. 安装必要的依赖2. 读取 Excel 文件3. 读

Spring 请求之传递 JSON 数据的操作方法

《Spring请求之传递JSON数据的操作方法》JSON就是一种数据格式,有自己的格式和语法,使用文本表示一个对象或数组的信息,因此JSON本质是字符串,主要负责在不同的语言中数据传递和交换,这... 目录jsON 概念JSON 语法JSON 的语法JSON 的两种结构JSON 字符串和 Java 对象互转

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

SpringBoot使用GZIP压缩反回数据问题

《SpringBoot使用GZIP压缩反回数据问题》:本文主要介绍SpringBoot使用GZIP压缩反回数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot使用GZIP压缩反回数据1、初识gzip2、gzip是什么,可以干什么?3、Spr

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

数据库面试必备之MySQL中的乐观锁与悲观锁

《数据库面试必备之MySQL中的乐观锁与悲观锁》:本文主要介绍数据库面试必备之MySQL中乐观锁与悲观锁的相关资料,乐观锁适用于读多写少的场景,通过版本号检查避免冲突,而悲观锁适用于写多读少且对数... 目录一、引言二、乐观锁(一)原理(二)应用场景(三)示例代码三、悲观锁(一)原理(二)应用场景(三)示例