当前位置: 首页 > news >正文

数据库 事务隔离级别 深入理解数据库事务隔离级别:脏读、不可重复读、幻读与串行化

概述

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
在这里插入图片描述

隔离级别一共有四种:

  • 读未提交:READ UNCOMMITTED

    允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED、

    要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE READ

    确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化:SERIALIZABLE

    确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√(默认)
REPEATABLE READ×√(默认)
SERIALIZABLE

各种级别的详细解释

脏读 (Dirty Read)

定义:脏读发生在一个事务读取了另一个未提交事务修改的数据时。

发生场景:隔离级别为读未提交(READ UNCOMMITTED)时。

示例说明:

-- 事务A
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1; -- 未提交-- 事务B
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 可能读到200(脏数据)-- 事务A
ROLLBACK; -- 回滚后余额恢复原值,但事务B已读到不存在的数据

危害:基于可能回滚的临时数据做出决策,导致业务逻辑错误。

不可重复读 (Non-Repeatable Read)

定义:在同一事务中,两次读取同一数据得到不同结果。

发生场景:隔离级别为读已提交(READ COMMITTED)或更低时。

示例说明:

-- 事务A
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 第一次读取,返回1000-- 事务B
BEGIN;
UPDATE accounts SET balance = 1500 WHERE id = 1;
COMMIT;-- 事务A
SELECT balance FROM accounts WHERE id = 1; -- 第二次读取,返回1500
COMMIT;

危害:破坏了事务内数据一致性视图,导致校验结果不可靠。

幻读 (Phantom Read)

定义:在同一事务中,两次相同查询返回的结果集行数不同。

发生场景:隔离级别为可重复读(REPEATABLE READ)或更低时。

示例说明:

-- 事务A
BEGIN;
SELECT * FROM accounts WHERE balance > 1000; -- 返回2条记录-- 事务B
BEGIN;
INSERT INTO accounts VALUES (3, '王五', 2000);
COMMIT;-- 事务A
SELECT * FROM accounts WHERE balance > 1000; -- 返回3条记录
COMMIT;

危害:基于过时的结果集前提进行操作,导致数据不一致。

串行化 (Serializable)

定义:最高隔离级别,强制事务串行执行。

工作机制:通过强锁机制(通常是表级锁)确保事务完全隔离。

示例说明:

-- 事务A
BEGIN;
SELECT * FROM accounts FOR UPDATE; -- 加锁-- 事务B
BEGIN;
SELECT * FROM accounts; -- 可能被阻塞或超时

优缺点:

优点:完全保证数据一致性

缺点:性能最低,并发性差

关于读已提交和可重复读

这是一个非常核心且容易混淆的概念。我们用一个非常详细的例子来把 读已提交 (Read Committed) 和 可重复读 (Repeatable Read) 彻底讲清楚。

想象一个简单的银行账户表 accounts:

idnamebalance
1张三1000
2李四2000

现在有两个并发的事务:事务A 和 事务B。

场景一:读已提交 (Read Committed)

核心思想: 一个事务只能读到其他事务已经提交的修改。它保证了你不会读到“脏数据”,但不保证在同一次事务中多次读取同一数据会得到相同的结果。

隔离级别设定:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

事件时序:

时间事务A事务B说明
T1BEGIN;事务A开始
T2BEGIN;事务B开始
T3SELECT balance FROM accounts WHERE id = 1; → 1000事务A第一次查询张三的余额,结果是1000。
T4UPDATE accounts SET balance = 1500 WHERE id = 1;COMMIT; 事务B将张三的余额增加500并提交了! 现在数据库里的真实数据已经是1500了。
T5SELECT balance FROM accounts WHERE id = 1; → 1500关键点来了! 事务A第二次查询。因为它处于“读已提交”级别,它会读取最新已提交的数据,所以看到1500。
T6COMMIT;事务A结束。

事务A的感受:
“我在这个事务里,第一次查余额是1000元,过了一会儿再查,莫名其妙变成了1500元!这钱是谁给我打的?我的查询结果不可重复!”

这就是“不可重复读”。 读已提交级别允许这种现象发生。

可重复读 (Repeatable Read)

核心思想: 在同一个事务中,多次读取同一数据的结果是一致的。就像在事务开始的那一刻,给整个数据库拍了一张快照(Snapshot),之后在这个事务里所有的读操作都基于这个快照,看不到其他事务提交的新数据。

隔离级别设定:

SET TRANSACTION ISISOLATION LEVEL REPEATABLE READ;
时间事务A事务B说明
T1BEGIN;事务A开始。就在这一刻,数据库为事务A创建了一个“一致性视图”或“快照”。
T2BEGIN;事务B开始。
T3SELECT balance FROM accounts WHERE id = 1; → 1000事务A第一次查询,从快照中读取数据,结果是1000。
T4UPDATE accounts SET balance = 1500 WHERE id = 1;COMMIT; 事务B再次将张三的余额增加500并提交了! 数据库真实数据变为1500。
T5SELECT balance FROM accounts WHERE id = 1; → 1000最关键的差别! 事务A第二次查询。它不会去读最新的数据,而是依然从它开始时的那个快照里读取数据。所以它看到的还是1000元!
T6COMMIT;事务A结束。当事务A提交后,这个快照就被释放了。

事务A的感受:
“太神奇了!不管外面世界如何变化,在我这个事务的生命周期内,我每次查张三的余额都是1000元,结果始终如一,可以重复读。”

这就是“可重复读”。 它解决了不可重复读的问题。

核心机制:MVCC (多版本并发控制)

这两种级别在现代数据库(如MySQL InnoDB, PostgreSQL)中,通常通过MVCC来实现。

  • 读已提交:在每条SELECT语句执行的瞬间,创建一个快照。所以每次读都能看到最新已提交的数据。

  • 可重复读:在事务开始(BEGIN)后的第一条SELECT语句执行时创建快照(有些数据库在BEGIN时就创建)。这个快照会贯穿整个事务的生命周期。

总结对比表格

特性读已提交 (Read Committed)可重复读 (Repeatable Read)
核心保证只读已提交的数据同一事务内读取可重复
解决什么问题脏读 (Dirty Read)脏读 + 不可重复读 (Non-Repeatable Read)
快照创建时机每条SELECT语句开始时事务开始时(或第一条SELECT时)
看到其他事务的提交是,立即看到否,整个事务期间都看不到
性能 高略低于读已提交(需要维护更久的快照)
常用场景绝大多数业务场景,如网上论坛、新闻站对数据一致性要求更高的场景,如银行账户查询、对账业务

简单记法:

读已提交:你提交了,我就能看见。(结果可能变)

可重复读:不管你有没有提交,我自始至终只看我开始时的样子。(结果不变)

http://www.dtcms.com/a/389888.html

相关文章:

  • 从“纸面”到“人本”:劳务合同管理的数字化蜕变
  • ARM架构——学习时钟7.2
  • VS Code 调试配置详解:占位符与语言差异
  • 锁 相关知识总结
  • caffeine 发生缓存内容被修改以及解决方案-深度克隆
  • rust编写web服务06-JWT身份认证
  • 《怪猎:荒野》制作人:PC平台对日本游戏非常重要
  • 大模型训练框架(二)FSDP
  • MySQL——系统数据库、常用工具
  • 蓝桥杯题目讲解_Python(转载)
  • 性能测试监控实践(九):性能测试时,监控docker微服务资源利用率和分析
  • TCP,UDP和ICMP
  • Python语法学习篇(七)【py3】
  • 网页控制鼠标 查看鼠标位置
  • PIT 定时器
  • 【题解】 [蓝桥杯 2019 省 B] 特别数的和
  • 数字隔离器,串口通信的安全之“芯”
  • 山脊图 (Ridgeline Plot):使用 joypy 库,优雅地比较多组数据的分布情况
  • Linux 进程同步以及僵尸进程等知识介绍
  • Python进程和线程
  • 斐波那契数列的递归和迭代实现
  • 时空预测论文分享:规则知识 因果预测框架 面向研究的评估体系 主动适应漂移
  • 《WINDOWS 环境下32位汇编语言程序设计》第18章 ODBC数据库编程
  • linux入门(3)
  • 任意版本GitLens vscode插件破解邪修秘法
  • Redis最佳实践——热点数据缓存详解
  • font简写和CSS的继承性
  • 高性能服务器配置经验指南6——BIT校园网在ubuntu中的自动检查连接状况脚本使用
  • SQL 连接详解:内连接、左连接与右连接
  • C2000基础-TIM介绍及使用