MySQL数据库事务的ACID之隔离性实验实用指南
平时做技术实践时,很多问题不是概念不会,而是细节没串起来。拿“MySQL数据库事务的ACID之隔离性实验”来说,它看着像小点,放到项目里常会牵出环境、配置、兼容性和维护成本。下面按实际采用顺序,把思路、关键写法和容易踩坑的地方讲清楚,便于大家直接对照操作。
ACID特性简介
- 原子性(Atomicity)
事务被视为一个不可分割的最小单位,它要么完全执行,要么完全不执行。 - 一致性(Consistency)
一致性保证了事务的执行将数据库从一个一致的状态转变到另一个一致的状态。 - 隔离性(Isolation)
从实现思路看,隔离性是指当多个事务同时对数据库进行操作时,每个事务都是独立的,一个事务的操作不会影响到其他事务。 - 持久性(Durability)
实际处理时,持久性意味着一旦事务被提交,它对数据库的修改就是永久性的,即使系统发生故障也不会丢失。
隔离性(Isolation)
- 隔离性(Isolation)的定义
实际处理时,隔离性(Isolation)确保同时发执行的事务是隔离的,即一个事务的执行不会被其他事务干扰。
落到代码里,这个特性是借助事务隔离级别来实现的,不同的隔离级别能够解决不同的同时发事务中的问题,但同时也会在性能和一致性之间做出权衡。
隔离性的作用与级别
理解这一步时,隔离性的主要作用是控制事务之间的可见性,即一个事务对其他事务的影响。SQL标准定义了四个隔离级别:- 理解这一步时,读未提交(Read Uncommitted):最低的隔离级别,允许事务读取未被其他事务提交的更改。这可能导致“脏读”,即读取到其他事务未提交的数据。
- 在这个场景下,读提交(Read Committed):允许事务读取同时仅读取已经被其他事务提交的更改。这个级别能够避免脏读,但仍然可能遇到“不可重复读”的问题,即在同一事务中,多次读取同一数据集合可能会得到不同的结果。
- 实际处理时,可重复读(Repeatable Read):确保在同一事务中多次读取同一数据集合时,结果是一致的,避免了不可重复读的问题。但是,它可能遇到“幻读”,即当其他事务插入或删除行时,当前事务的后续查询会“看到”新的行。
- 在这个场景下,串行化(Serializable):最高的隔离级别,借助强制事务串行执行,避免了脏读、不可重复读和幻读的问题。这提供了最强的一致性保证,但性能开销也最大。
下面对该问题进行实验:
读未提交(Read Uncommitted)
首先,查看数据库事务隔离级别,同时将隔离级别设置为读未提交。
采用如下所示命令查看事务隔离级别。
SELECT @@session.transaction_isolation;
查询到隔离级别为可重复读。
设置隔离级别为读未提交。
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
落到代码里,然后开启两个会话,每个会话中开启一个事务,模拟两个事务同时发读取修改的情况。观察脏读情况。
首先,数据库中有两条数据。
会话一:
start transaction;
-- a语句
select a.id,a.username
from Appointment a
where a.id = 1;
-- b语句
update Appointment a
set username = 'nihao12'
where a.id = 1;
-- c语句
select a.id,a.username
from Appointment a
where a.id = 1;
commit;
会话二:
start transaction;
-- a语句
select a.id,a.username
from Appointment a
where a.id = 1;
commit;
首先,会话一开启事务同时执行a语句,会话二开启事务并执行a语句。
结果如下所示:
会话一:
会话二:
结合项目来看,下面,会话一执行b语句,执行完后执行c语句观察结果。会话二在会话一执行完b语句后再次执行a语句。结果如下所示:
会话一:
会话二:
我们发现,会话二中读取到了会话一中未提交的数据。发生了脏读。
读已提交
根据读未提交中的内容将隔离级别设置为读已提交。
落到代码里,然后开启两个会话,每个会话中开启一个事务,模拟两个事务同时发读取修改的情况。观察脏读情况。
首先,数据库中有两条数据。
会话一:
start transaction;
-- a语句
select a.id,a.username
from Appointment a
where a.id = 1;
-- b语句
update Appointment a
set username = 'nihao12'
where a.id = 1;
-- c语句
select a.id,a.username
from Appointment a
where a.id = 1;
commit;
会话二:
start transaction;
-- a语句
select a.id,a.username
from Appointment a
where a.id = 1;
commit;
首先,会话一开启事务同时执行a语句,会话二开启事务并执行a语句。
结果如下所示:
会话一:
会话二:
结合项目来看,下面,会话一执行b语句,执行完后执行c语句观察结果。会话二在会话一执行完b语句后再次执行a语句。结果如下所示:
会话一:
会话二:
我们发现,会话二中没有读取到会话一中未提交的数据。
会话一提交数据,会话二继续执行a语句读取数据。结果如下所示:
会话一:
会话二:
结果表明,会话二读取到了会话一提交的数据。
可重复读
对于可重复读,和上面的步骤一样,我们从开启事务开始看起。
开启事务后,会话一执行a语句,会话二执行a语句。结果如下所示:
会话一:
会话二:
会话一执行b语句,随后执行c语句。会话二执行a语句。结果如下所示:
会话一:
会话二:
会话二无法读取到会话一未提交的数据。
会话一提交,会话二继续执行a语句。结果如下所示:
会话一:
会话二:
结果表明会话二无法读取到会话一提交的数据。会话二中数据可重复读取。
下面将会话一,会话二中的代码改为下面的情况:
会话一:
start transaction;
-- a语句
select *
from Appointment a
where a.id<10;
-- b语句
insert INTO Appointment(id,username,id_card,department,date,time)
values(3,'nihao5',1234,'uuuu1','2010/09/08','1231');
-- c语句
select *
from Appointment a
where a.id<10;
commit;
会话二:
start transaction;
-- a语句
select *
from Appointment a
where a.id<10;
-- b语句
update Appointment a
set a.time = '1232'
where a.id<10;
-- c语句
select *
from Appointment a
where a.id<10;
commit;
实际处理时,步骤会话一会话二结果1start transactionstart transaction2执行a语句执行完会话一a语句执行会话二a语句内容相同3执行b语句4执行c语句执行完会话一c语句执行会话二a语句会话一中数据有两条,会话二中数据有两条(会话二a语句为快照读,读取不到会话一修改的内容。可重复读避免了读幻读落到代码里,。)5commit6执行a语句会话二中数据有2条7执行b语句更新结果为3条数据(写幻读)8执行c语句查出来了2条数据
从实现思路看,我们发现在会话二中更新后发生了幻读的现象(快照读的情况下,会发生写幻读的情况)。
为了避免写幻读的情况。能够尝试加锁。将会话二的代码改为如下所示所示:
start transaction;
-- a语句
select *
from Appointment a
where a.id<10 for update;
-- b语句
update Appointment a
set a.time = '1231'
where a.id<10;
-- c语句
select *
from Appointment a
where a.id<10;
commit;
实际处理时,上述代码中select … for update会加锁(具体加什么锁先不进行探讨)。执行后,会话一中的插入会被阻塞。
执行序列和结果如下所示表所示:
| 步骤 | 会话一 | 会话二 | 结果 |
|---|---|---|---|
| 1 | start transaction | start transaction | |
| 2 | 执行a语句 | 执行完会话一a语句执行会话二a语句 | 内容相同,会话二对内容加锁。 |
| 3 | 执行b语句 | 会话一插入语句被阻塞 | |
| 4 | 执行b语句更新 | 更新两条数据 | |
| 5 | 执行c语句 | 查出来两条数据 | |
| 6 | commit | 提交,锁释放 | |
| 7 | 再次执行b语句 | 插入成功 | |
| 8 | 执行c语句 | 三条数据 | |
| 9 | commit |
从实现思路看,MySQL 的事务隔离级别是 会话级设置,每个事务仅受自身隔离级别的约束,不影响其他事务。
隔离级别是会话独立的:实际处理时,一个事务的隔离级别只决定它 如何看到其他事务的数据,不会强制其他事务遵循自己的级别。
从实现思路看,高隔离级别事务不会“限制”低隔离级别事务的行为:比如,即使事务 A 设置为 SERIALIZABLE,事务 B 仍可按 READ UNCOMMITTED 运行,同时读取 A 未提交的数据(如果 A 允许)。
从实现思路看,实际影响体现在数据可见性上:低隔离级别的事务可能看到高隔离级别事务未提交的数据(脏读),或在高隔离事务中看到不一致快照。
注:对于以上实验,两个会话的隔离级别都是相同的。能够自己试下两个隔离级别不同的情况。
总结
在这个场景下,总的来说,mysql事务ACID适合结合实际项目边做边理解。先抓住核心思路,再逐步补上细节和边界处理,最后效果会更稳定,也更容易复用。
-
09.30
Mysql子查询5大核心用法代码示例实用指南
-
09.30
C#代码实现方式更改或删除PDF中的超链接实用指南
-
09.30
Jev报错401 / api_key_required怎么解决?TypeSafe AI官方错误码表与排查步骤完整指南
-
09.30
what-did-ai-do:AI Agent 工具实践指南
-
09.30
MySQL数据库事务的ACID之隔离性实验实用指南
-
09.30
free-token-eggs:AI Agent 工具实践指南
-
-
下载
- |
-
-
下载
- 《行尸走肉第一章》免安装中文汉化硬盘版下载
- 单机|436 MB
- 一款以动作冒险为主题的游戏
-
-
下载
- 《街头霸王X铁拳》免安装中文汉化硬盘版下载
- 单机|111MB
- 一款非常好玩的格斗游戏
-
-
下载
- |
-
-
下载
- 《暗黑破坏神3》免安装繁体中文正式版下载
- 单机|7630 MB
- 一款以角色扮演为主题的游戏
-
-
下载
- 《马克思佩恩3》免安装硬盘版下载
- 单机|27033 MB
- 一款以第三人称射击为主题的游戏