Mysql事务和原理

一、事务(Transaction)        

本质
  • 并发控制的基本单元,用户定义的操作序列,具有原子性(要么全做,要么全不做)。
目的
  • 将数据库从一致性状态转换为另一种一致性状态,确保系统状态完整正确。
组成
  • 可由单条 SQL 语句(如 INSERT)或一组复杂 SQL 语句组成。
特征
  • 提交时确保所有修改持久化全部回滚
  • 是访问和更新数据库数据项的独立执行单元。

1. 事务控制语句(MySQL InnoDB)

-- 关闭自动提交(手动管理事务)  
SET autocommit = 0;  

-- 显式开启事务  
START TRANSACTION; 或 BEGIN;  

-- 提交事务(持久化修改)  
COMMIT;  

-- 回滚事务(撤销未提交修改)  
ROLLBACK;  

-- 创建保存点  
SAVEPOINT savepoint_name;  

-- 回滚到保存点  
ROLLBACK TO SAVEPOINT savepoint_name;  

-- 删除保存点  
RELEASE SAVEPOINT savepoint_name;  

2. ACID 特性

1. 原子性(Atomicity)
  • 定义:事务操作要么全执行(提交),要么全不执行(回滚),不可分割。
  • 实现:通过 undo log 记录操作逆过程,回滚时执行逆运算。
2. 隔离性(Isolation)
  • 定义:控制事务间的相互影响程度,应对脏读、不可重复读、幻读问题。
  • 实现
    • MVCC(多版本并发控制):通过行版本快照实现一致性非锁定读,提升读性能;
    • 锁机制:处理并发写操作,支持表锁、页锁、行锁(InnoDB 默认为行锁)。
3. 持久性(Durability)
  • 定义:事务提交后,修改永久保存(即使宕机)。
  • 实现:通过 redo log 记录物理修改(如页偏移、数据),故障时通过日志恢复。
4. 一致性(Consistency)
  • 定义:事务前后数据库满足完整性约束(如唯一键、外键)。
  • 实现:依赖原子性、隔离性、持久性共同保证。

3. 隔离级别(ISO/ANSI 标准)

隔离级别描述锁机制MySQL 默认
READ UNCOMMITTED读未提交(可能读到脏数据),写加排他锁,读不加锁。写锁(提交 / 回滚释放)
READ COMMITTED读已提交(RC),通过 MVCC 读取最新已提交快照,避免脏读。MVCC + 写锁
REPEATABLE READ可重复读(RR),通过 MVCC 读取事务开始时的快照,避免不可重复读。MVCC + 写锁
SERIALIZABLE可串行化,读加共享锁,强制事务串行执行,完全避免并发问题。读写锁(串行化)

1. READ UNCOMMITTED(读未提交)

问题:可能出现脏读(Dirty Read)

 
  • 描述:事务可以读取其他未提交事务的数据变更,数据可能是临时的、未确认的。
  • 锁机制
    • 写操作加排他锁(X 锁),阻止其他事务写,但不阻止读。
    • 读操作不加锁,直接读取最新数据(可能未提交)。
  • MySQL 默认:否
  • 示例场景:银行转账中的脏读
    • 用户 A发起转账 100 元给用户 B,事务未提交:
      START TRANSACTION;
      UPDATE accounts SET balance = balance - 100 WHERE id = 'A'; -- 未提交
      
    • 用户 B此时查询余额(隔离级别为读未提交):
      SELECT balance FROM accounts WHERE id = 'B'; -- 假设此时读取到临时增加的100元(脏数据)
      
    • 用户 A的事务回滚(ROLLBACK),用户 B 读到的 100 元会消失,导致数据不一致。
  • 适用场景:几乎不使用,因为脏读风险高,仅适用于允许临时脏数据的极弱一致性场景(如日志统计)。

2. READ COMMITTED(读已提交,RC)

解决:避免脏读,但可能出现不可重复读(Non-Repeatable Read)

  • 描述:事务只能读取其他已提交事务的数据变更,每次读取时获取最新已提交的快照
  • 锁机制
    • 写操作加排他锁(X 锁),提交后释放锁。
    • 读操作通过 MVCC(多版本并发控制) 读取历史快照,不加锁(一致性非锁定读)。
  • MySQL 默认:否(但 Oracle 默认是 RC)
  • 示例场景:订单状态更新的不可重复读
    • 事务 1(用户查询订单状态)
      START TRANSACTION;
      SELECT status FROM orders WHERE id = 1; -- 第一次查询,状态为"待支付"
      
    • 事务 2(商家修改订单状态)(隔离级别 RC,已提交):
      UPDATE orders SET status = '已支付' WHERE id = 1; -- 提交事务
      
    • 事务 1 再次查询
      SELECT status FROM orders WHERE id = 1; -- 第二次查询,状态变为"已支付"(前后结果不一致,不可重复读)
      
  • 适用场景:大多数 OLTP 系统(如电商、票务),平衡一致性和性能,避免脏读。

3. REPEATABLE READ(可重复读,RR)

解决:避免不可重复读,但可能出现幻读(Phantom Read)

  • 描述:事务在启动时获取初始快照,整个事务期间读取的数据始终与初始快照一致,即使其他事务已提交新数据。
  • 锁机制
    • 写操作加排他锁(X 锁),提交后释放锁。
    • 读操作通过 MVCC 读取事务开始时的快照,不加锁(一致性非锁定读)。
    • MySQL 特殊处理:通过 间隙锁(Gap Lock) 避免幻读(默认配置下)。
  • MySQL 默认:是
  • 示例场景:库存扣减的可重复读
    • 库存表初始数据goods_id=1stock=10
    • 事务 1(用户 A 查询库存并下单)
      START TRANSACTION;
      SELECT stock FROM goods WHERE goods_id = 1; -- 第一次查询,stock=10
      -- 模拟业务处理(未提交)
      
    • 事务 2(用户 B 购买并提交)
      UPDATE goods SET stock = stock - 1 WHERE goods_id = 1; -- 提交后,stock=9
      
    • 事务 1 再次查询
      SELECT stock FROM goods WHERE goods_id = 1; -- 第二次查询,stock仍为10(与初始快照一致,可重复读)
      
    • 幻读示例:若事务 1 查询stock < 20的所有商品,事务 2 插入一条stock=15的新记录,事务 1 再次查询会看到新记录(幻读),但 MySQL 通过间隙锁默认避免此问题。
  • 适用场景:需要事务内数据一致性的场景(如金融交易、库存扣减),MySQL 默认级别,性能与一致性平衡较好。

4. SERIALIZABLE(可串行化)

解决:完全避免脏读、不可重复读、幻读,强制串行执行

  • 描述:事务通过 ** 共享锁(S 锁)和排他锁(X 锁)** 强制串行执行,读加 S 锁,写加 X 锁,读写互斥。
  • 锁机制
    • 读操作加共享锁(S 锁),写操作加排他锁(X 锁),两者互斥(读时不能写,写时不能读)。
    • 所有事务按顺序执行,相当于单线程处理。
  • MySQL 默认:否
  • 示例场景:火车票抢购的强一致性
    • 剩余票数train_id=1tickets=1
    • 事务 1(用户 A 查询并购票)
      START TRANSACTION;
      SELECT tickets FROM train WHERE train_id = 1 LOCK IN SHARE MODE; -- 加S锁,读取tickets=1
      -- 模拟业务处理
      UPDATE train SET tickets = tickets - 1 WHERE train_id = 1; -- 升级为X锁,提交后释放锁
      COMMIT;
      
    • 事务 2(用户 B 同时购票)
      START TRANSACTION;
      SELECT tickets FROM train WHERE train_id = 1 LOCK IN SHARE MODE; -- 阻塞,等待事务1释放锁
      
    • 事务 1 提交后,事务 2 才能执行,避免超卖(强一致性)。
  • 适用场景:极少数需要绝对一致性且不考虑性能的场景(如银行核心交易),性能最差,并发吞吐量极低。

总结对比

隔离级别脏读不可重复读幻读锁机制并发性能MySQL 默认
READ UNCOMMITTED允许允许允许写 X 锁,读无锁最高
READ COMMITTED禁止允许允许MVCC + 写 X 锁
REPEATABLE READ禁止禁止部分禁止MVCC + 写 X 锁 + 间隙锁
SERIALIZABLE禁止禁止禁止读写锁(串行化)最低
 

选择建议

  • 优先使用 REPEATABLE READ(MySQL 默认),平衡一致性和性能,适合大多数场景。
  • 若需更高一致性,可通过业务层逻辑(如唯一索引、乐观锁)补充,而非直接使用 SERIALIZABLE。

 设置隔离级别:

-- 设置全局隔离级别(需重启或新会话生效)  
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;  
SET @@global.tx_isolation = 'REPEATABLE READ';  

-- 设置当前会话隔离级别(仅当前连接生效)  
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;  
SET @@session.tx_isolation = 'REPEATABLE READ';  

-- 查看隔离级别  
SELECT @@global.tx_isolation;  -- 全局  
SELECT @@session.tx_isolation; -- 当前会话  

手动加锁语句:

SELECT ... LOCK IN SHARE MODE;  -- 共享锁(S 锁),读锁  
SELECT ... FOR UPDATE;          -- 排他锁(X 锁),写锁  

锁查看:

-- 查看 InnoDB 锁信息  
SELECT * FROM information_schema.innodb_locks;  

4. 锁

        锁机制用于管理对共享资源的并发访问;用来实现事务的隔离级别;

4.1 锁类型

        共享锁和排他锁都是行级锁;MySQL当中事务采用的是粒度锁;针对表(B+树)、页(B+树叶子 节点)、行(B+树叶子节点当中某一段记录行)三种粒度加锁; 意向共享锁和意向排他锁都是表级别的锁;

4.2 共享锁 (S)

         事务读操作加的锁;对某一行加锁;

         在 SERIALIZABLE 隔离级别下,默认帮读操作加共享锁;

        在 REPEATABLE READ 隔离级别下,需手动加共享锁,可解决幻读问题;

        在 READ COMMITTED 隔离级别下,没必要加共享锁,采用的是 MVCC;

         在 READ UNCOMMITTED 隔离级别下,既没有加锁也没有使用 MVCC;

4.3 排它锁 (X)

        事务删除或更新加的锁;对某一行加锁; 在4种隔离级别下,都添加了排他锁,事务提交或事务回滚后释放锁;

4.4 意向共享锁 (IS)

        对一张表中某几行加的共享锁;

4.5 意向排它锁 (IX)

        对一张表中某几行加的排他锁; 目的:为了告诉其他事务,此时这条表被一个事务在访问;作用:排除表级别读写锁 (全面扫描 加锁);

        IX 锁(Intention Exclusive Lock)是表级别的意向锁,其核心作用是声明事务即将在表的某些行上施加排它锁(X 锁)。它是一种 “协调锁”,用于告知其他事务当前事务对表的操作意图,避免表锁与行锁的冲突。

  • 作用范围:表级别(非行级别)。
  • 设计目的:当事务需要对表中的某一行加行级 X 锁时,必须先在表级别加 IX 锁。这是为了快速判断是否有其他事务正在占用表级锁(如全表写锁),避免直接遍历所有行检查行锁的开销。
  • 与其他锁的兼容关系
    • 与表级共享锁(IS 锁)兼容;
    • 与表级排它锁(X 锁)互斥;
    • 不影响行级锁的加锁逻辑(行级 X 锁需要先加表级 IX 锁)。

 

4.6 锁的兼容性

SXISIXAI
S兼容冲突兼容冲突冲突
X冲突冲突冲突冲突冲突
IS兼容冲突兼容兼容兼容
IX冲突冲突兼容兼容兼容
AI冲突冲突兼容兼容冲突

        由于 InnoDB 支持的是行级别的锁,意向锁并不会阻塞除了全表扫描以外的任何请求;
意向锁之间是互相兼容的;
        IS 只对排他锁不兼容;
        当想为某一行添加 S 锁,先自动为所在的页和表添加意向锁 IS,再为该行添加 S 锁;
        当想为某一行添加 X 锁,先自动为所在的页和表添加意向锁 IX,再为该行添加 X 锁;
        当事务试图读或写某一条记录时,会先在表上加上意向锁,然后才在要操作的记录上加读锁或写锁。这样判断表中是否有记录加锁就很简单了,只要看下表上是否有意向锁就行了。意向锁之间是不会产生冲突的,也不和 AUTO_INC 锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。

5. 锁算法

  1. Record Lock(记录锁)

    • 定义:锁定单个行记录的锁。
    • 示例:对某一行数据加锁,仅限制其他事务对该行的修改,不影响其他行。
  2. Gap Lock(间隙锁)

    • 定义:锁定一个范围,但不包含记录本身,用于 可重复读(REPEATABLE READ) 及以上隔离级别,防止幻读(其他事务插入新记录导致当前事务查询结果变化)。
    • 条件:当 innodb_locks_unsafe_for_binlog = 0 时启用。
    • 示例:若索引列有值 1020,间隙锁可能锁定 (10, 20) 区间,阻止其他事务在该区间插入新值(如 15)。
  3. Next - Key Lock(临键锁)

    • 定义:记录锁(Record Lock)与间隙锁(Gap Lock)的组合,既锁定记录本身,又锁定记录前的间隙(左开右闭区间)。
    • 作用:在可重复读隔离级别下,彻底解决幻读问题,既防止修改已有记录,也防止插入新记录。
    • 示例:锁定索引值 10 这一行,同时锁定 (某个下限, 10] 的区间。
  4. Insert Intention Lock(插入意向锁)

    • 定义:插入操作时产生的锁,用于表示事务插入数据的意向。
    • 特点:多个事务向同一索引间隙插入数据时,插入意向锁之间相互兼容,不会互相阻塞。
    • 示例:假设索引列有值 4 和 7,两个事务分别插入 5 和 6,各自生成在 (4, 7) 区间的插入意向锁,不会互相阻塞,因为插入行不冲突。
  5. AUTO - INC Lock (AI 锁)

    • 定义:一种特殊的表级锁,用于 AUTO_INCREMENT 约束的插入操作。
    • 机制:早期采用表级锁,性能较差;从 MySQL 5.1.22 开始引入轻量级机制,提升高并发插入性能,仅在分配自增值时短暂锁定。
GAP (持有)Insert Intention (持有)Record (持有)Next - key (持有)
GAP (请求)兼容兼容兼容兼容
Insert Intention (请求)冲突兼容兼容冲突
Record (请求)兼容兼容冲突冲突
Next - key (请求)兼容兼容冲突冲突
  • 横向:已持有的锁类型(如 GAPInsert Intention 等)。
  • 纵向:正在请求的锁类型。
  • 规则
    • 若请求锁与已持锁在表格交叉处为 “兼容”,则可成功加锁;若为 “冲突”,则需等待。
    • 示例:
      • 请求 GAP 锁,已持有 GAP 锁 → 兼容(可加锁)。
      • 请求 Insert Intention 锁,已持有 GAP 锁 → 冲突(需等待)。
  • 特殊说明
    • 一个事务获取插入意向锁,对其他事务无阻塞影响。
    • 若其他事务已加 Gap Lock 或 Next - Key Lock,插入意向锁会被阻塞,因为前两者包含的范围可能与插入操作冲突。

6.  MVCC

                MVCC(Multi - Version Concurrency Control,多版本并发控制)是 InnoDB 存储引擎实现一致性非锁定读的关键机制,其核心是通过维护数据的多个版本,结合读视图(Read View)判断数据对当前事务的可见性,从而避免读操作被写操作阻塞,提升并发性能。以下是其原理的详细讲解:

一、MVCC 在不同隔离级别的应用

  • Read Committed(读已提交):每次 SELECT 操作都会生成新的快照,读取的是被锁定行的最新一份快照数据。这意味着在同一个事务中多次读取同一条数据,可能因其他事务提交修改而出现结果不一致。
  • Repeatable Read(可重复读):在事务启动时生成一个快照,整个事务过程都使用该快照。这样能保证事务期间读到的数据始终是事务启动时的记录,避免不可重复读和幻读问题。

二、Read View(读视图)

  • 定义与作用:Read View 是 MVCC 实现的核心数据结构,用于判断数据版本对当前事务的可见性。
  • 创建时机
    • Read Committed:每次执行 SELECT 时创建新的 Read View。
    • Repeatable Read:事务启动时创建 Read View,整个事务期间复用。
  • 构成
    • m_ids:创建 Read View 时,当前数据库中活跃事务(未提交)的事务 ID 列表。
    • min_trx_idm_ids 中的最小事务 ID。
    • max_trx_id:创建 Read View 时,为下一个事务分配的 ID(不一定是 m_ids 中的最大值)。
    • creator_trx_id:创建该 Read View 所在事务的 ID。

三、聚簇索引隐藏列

每条聚簇索引记录都包含以下隐藏列:

 
  • trx_id:最后一次修改该记录的事务 ID。
  • roll_pointer:回滚指针,指向 undo log 中该记录的旧版本。通过此指针可找到修改前的数据,用于实现多版本控制。

四、事务状态

  • 已提交的事务:其修改对其他事务的可见性由 Read View 判断。
  • 已启动未提交的事务:其修改对其他事务不可见(除非符合特定可见性规则)。
  • 还没开始的事务:与当前 Read View 无直接关联。

五、数据可见性判断流程

  1. 若 trx_id < min_trx_id:说明记录在 Read View 创建之前已提交,对当前事务可见。
  2. 若 trx_id >= max_trx_id:说明记录是在 Read View 创建之后生成的,对当前事务不可见。
  3. 若 min_trx_id <= trx_id < max_trx_id
    • 检查 trx_id 是否在 m_ids 列表中。
    • 若在列表中,说明生成该版本的事务仍处于活跃状态,该版本对当前事务不可见。
    • 若不在列表中,说明生成该版本的事务已提交,该版本对当前事务可见。

 快照读和当前读的区别:

  • 快照读

    • 操作:普通的 SELECT 语句(如 select * from table where?),不加锁。
    • 原理:通过 MVCC(多版本并发控制)读取历史版本数据,实现一致性非锁定读,无需等待锁,提升并发性能。
    • 数据版本:依赖隔离级别,Read Committed 每次读生成新快照(读最新已提交版本);Repeatable Read 事务启动时生成快照(读事务启动时的版本)。
  • 当前读

    • 操作:包括加锁的 SELECT(如 select... lock in share mode 共享锁、select... for update 排他锁),以及写操作(INSERTUPDATEDELETE)。
    • 原理:读取最新版本数据,且会对数据加锁,确保数据的实时性与操作互斥,避免其他事务干扰。
    • 数据版本:始终读取最新提交的数据版本。

示例

假设表中有一条记录 id = 1,初始 value = 'old',有事务 A 和事务 B:

快照读示例(以 Read Committed 隔离级别为例):
  1. 事务 A 执行 select * from table where id = 1;(快照读),读取到 value = 'old'
  2. 事务 B 执行 update table set value = 'new' where id = 1; 并提交,修改数据为 new
  3. 事务 A 再次执行 select * from table where id = 1;(快照读),因 Read Committed 重新生成快照,读取到 value = 'new'

        若为 Repeatable Read 隔离级别,事务 A 启动时生成快照,两次快照读都会读到 value = 'old'(除非事务 A 自身修改数据)。

当前读示例:
  1. 事务 A 执行 select * from table where id = 1 for update;(当前读,加排他锁),读取到最新的 value(假设为 old)。
  2. 事务 B 执行 update table set value = 'new' where id = 1;(当前读,需获取排他锁),因事务 A 已加锁,事务 B 被阻塞,直到事务 A 提交或回滚释放锁。
  3. 事务 A 提交后,事务 B 才能获取锁并执行更新,确保事务 A 读到的是最新且未被修改的数据(若有其他事务先修改,当前读会等待锁释放后读最新值)。

7. redolog 和 undolog

undo log(撤销日志)

  • 定义与作用:undo log 是 InnoDB 存储引擎生成的日志,用于事务回滚及实现 MVCC(多版本并发控制),保证事务的原子性(操作要么全部成功,要么全部失败)。
  • 原理:在事务开始时,记录数据修改前的版本(如更新前的旧值、插入前的空记录状态、删除前的记录内容)。通过 trx_id(事务 ID)标记修改所属事务,通过 roll_pointer(回滚指针)将同一事务的 undo log 串联成版本链。当事务回滚时,根据 undo log 的记录反向操作:
    • 对 UPDATE,将数据恢复为旧值;
    • 对 INSERT,执行 DELETE 移除新插入数据;
    • 对 DELETE,执行 INSERT 恢复被删数据。
  • 示例
    假设表中有一条记录 id=1value='old'。事务执行 UPDATE table SET value='new' WHERE id=1,undo log 会记录修改前的 value='old'。若事务回滚,根据 undo log 将 value 恢复为 old
  • 使用:由 InnoDB 引擎自动管理,无需手动干预。事务提交后,undo log 进入删除列表,由后台线程回收;在 MVCC 中,读操作通过 undo log 读取历史版本数据(如 SELECT 操作在特定隔离级别下借助 undo log 实现非阻塞读)。

redo log(重做日志)

  • 定义与作用:redo log 是 InnoDB 存储引擎生成的日志,用于保证事务的持久性(事务提交后,数据即使在系统崩溃时也不丢失)。
  • 原理:记录事务对数据页的物理修改(如 “某个表空间的某页,偏移量 X 处的值被改为 Y”)。事务提交时,先将修改写入 redo log(内存中的 redo log buffer),再刷盘到 redo log 文件。若系统崩溃,重启后 InnoDB 通过扫描 redo log,重新执行未完全刷盘的修改操作,确保数据落盘。
  • 示例
    事务对多个数据页进行修改(如插入、更新),这些操作先记录到 redo log。若事务提交后、数据刷盘前系统崩溃,重启后根据 redo log 重新执行这些修改,保证数据完整。
  • 使用
    • 由 InnoDB 引擎内部处理,通过参数 innodb_flush_log_at_trx_commit 控制刷盘策略:
      • 1(默认):每次事务提交时,强制将 redo log buffer 内容刷盘,保证强持久性,但性能略低;
      • 2:事务提交时,将 redo log 写入操作系统缓冲区,不立即刷盘,系统崩溃可能丢失少量数据,性能较高。

总结对比

日志类型核心作用记录内容保证特性典型场景
undo log事务回滚、MVCC数据修改前的旧版本原子性事务回滚、非锁定读(MVCC)
redo log崩溃后恢复数据数据页的物理修改操作持久性系统崩溃后恢复未刷盘的修改

二、 并发读异常

        数据库并发读异常主要包括 脏读、不可重复读、幻读,本质是多事务并发时,一个事务的写操作影响了另一个事务的读结果。以下是具体分析:

1.1 脏读(Dirty Read)

定义:一个事务读取到另一个未提交事务修改的数据(“脏数据”),若未提交事务回滚,读取的数据将无效。

示例(会话流程):
步骤Session A(修改事务)Session B(读取事务)结果说明
1SET @@tx_isolation='READ UNCOMMITTED';SET @@tx_isolation='READ UNCOMMITTED';双方设置隔离级别为 READ UNCOMMITTED(允许脏读)。
2BEGIN;BEGIN;事务启动。
3UPDATE account_t SET money=money+100 WHERE name='A';Session A 修改 A 的余额(未提交)。
4SELECT money FROM account_t WHERE name='A';Session B 读取到 A 的余额已增加 100(脏数据)。
5ROLLBACK;Session A 回滚,修改被撤销。
6SELECT money FROM account_t WHERE name='A';Session B 再次读取,发现余额恢复原值(之前读到的是脏数据)。
特点:
  • 产生条件:隔离级别为 READ UNCOMMITTED(最低隔离级别)。
  • 应用场景:读写分离架构中,从库(Slave)可设置 READ UNCOMMITTED,允许脏读以提升读性能(对数据实时性要求不高)。

1.2 不可重复读(Non-Repeatable Read)

定义:一个事务内两次读取同一记录,结果不一致(因另一个事务已提交修改)。

示例(会话流程):
步骤Session A(读取事务)Session B(修改事务)结果说明
1SET @@tx_isolation='READ COMMITTED';SET @@tx_isolation='READ COMMITTED';双方设置隔离级别为 READ COMMITTED(读已提交)。
2BEGIN;BEGIN;事务启动。
3SELECT money FROM account_t WHERE name='A';Session A 第一次读取 A 的余额(假设为 1000)。
4UPDATE account_t SET money=money+100 WHERE name='A'; COMMIT;Session B 修改 A 的余额并提交(余额变为 1100)。
5SELECT money FROM account_t WHERE name='A';Session A 第二次读取,发现余额变为 1100(与第一次不一致)。
特点:
  • 产生条件:隔离级别为 READ COMMITTED 或更低(如 READ UNCOMMITTED)。
  • 可接受性:因读取的是已提交数据,通常对业务影响较小(如大多数数据库默认隔离级别为 READ COMMITTED,如 Oracle、SQL Server)。

1.3 幻读(Phantom Read)

定义:一个事务内两次读取同一范围的记录,结果集不一致(因另一个事务插入 / 删除了符合条件的记录)。

示例(会话流程):
步骤Session A(读取事务)Session B(插入事务)结果说明
1SET @@tx_isolation='REPEATABLE READ';SET @@tx_isolation='REPEATABLE READ';双方设置隔离级别为 REPEATABLE READ(可重复读)。
2BEGIN;BEGIN;事务启动。
3SELECT * FROM account_t WHERE id >= 2;Session A 第一次查询 id≥2 的记录(假设结果为 id=2,3)。
4INSERT INTO account_t(id,name,money) VALUES(4,'D',1000); COMMIT;Session B 插入 id=4 的记录并提交。
5SELECT * FROM account_t WHERE id >= 2;Session A 第二次查询,结果包含 id=4(幻读:结果集新增记录)。
解决方法:
  • 加锁读:在 REPEATABLE READ 隔离级别下,使用 SELECT ... LOCK IN SHARE MODE(共享锁)或 SELECT ... FOR UPDATE(排他锁),通过 Next-Key Lock 锁定查询范围,阻止其他事务插入 / 删除。

1.4 三者区别总结

异常类型核心特征影响的事务操作数据状态(被读)
脏读读取未提交的 “脏数据”写(未提交)→ 读未提交(可能回滚)
不可重复读两次读同一记录结果不同(因已提交修改)写(已提交)→ 读已提交(修改后的值)
幻读两次读同一范围结果集不同(因插入 / 删除)写(已提交)→ 读已提交(新增 / 删除的记录)

二、丢失更新(Lost Update)

定义:两个事务并发修改同一数据,后提交的事务覆盖了先提交事务的修改,导致部分操作丢失。

类型与示例

(1)提交覆盖(更常见)

两个事务均提交修改,后提交的覆盖先提交的。

 
步骤Session A(事务 1)Session B(事务 2)结果说明
1SET @@tx_isolation='REPEATABLE READ';SET @@tx_isolation='REPEATABLE READ';双方设置隔离级别为 REPEATABLE READ
2BEGIN;BEGIN;事务启动。
3SELECT money FROM account_t WHERE name='A';(假设为 1000)SELECT money FROM account_t WHERE name='A';(假设为 1000)两个事务均读取到 A 的余额为 1000。
4UPDATE account_t SET money=1000+100 WHERE name='A';Session A 修改余额为 1100(未提交)。
5COMMIT;UPDATE account_t SET money=1000+100 WHERE name='A'; COMMIT;Session B 修改余额为 1100(覆盖 Session A 的修改)。
(2)回滚覆盖(数据库自动避免)

一个事务提交,另一个事务回滚,回滚的事务覆盖已提交的修改。但数据库通过锁机制(如行锁)可避免此情况。

解决方法:

  • 加锁:使用 SELECT ... FOR UPDATE 对记录加排他锁,确保一次仅一个事务修改。
  • 乐观锁:通过版本号(如 version 字段)判断数据是否被修改,若版本号不符则重试。

三、死锁(Deadlock)

定义:两个或多个事务因争夺锁资源,导致互相等待无法继续执行的现象。

3.1 常见类型

(1)相反加锁顺序死锁
  • 场景:两个事务以相反顺序访问同一组资源(如表或行)。
  • 示例
    • Session A 先锁 id=1 再锁 id=2
    • Session B 先锁 id=2 再锁 id=1
    • 若两者同时执行,会因互相等待对方释放锁而死锁。
(2)锁冲突死锁(RR 隔离级别常见)
  • 场景:一个事务持有 Gap Lock(间隙锁)或 Next-Key Lock(临键锁),另一个事务尝试插入数据时请求 Insert Intention Lock(插入意向锁),因锁冲突导致等待。

3.2 检测与查看

  • 系统表
    • information_schema.INNODB_TRX:查看当前事务。
    • information_schema.INNODB_LOCKS:查看当前锁。
    • information_schema.INNODB_LOCK_WAITS:查看锁等待关系。
  • 监控参数
    • 开启死锁日志:SET GLOBAL innodb_print_all_deadlocks=ON;(死锁信息记录到错误日志)。

3.3 解决与避免策略

策略说明
调整加锁顺序确保所有事务按相同顺序访问资源(如按 id 升序加锁)。
降低隔离级别如从 REPEATABLE READ 调整为 READ COMMITTED,减少锁范围。
优化索引避免全表扫描(全表扫描会为所有行加锁,增加死锁概率)。
拆分大事务大事务持有锁时间长,拆分为小事务可减少锁竞争。

四、隔离级别与并发异常的关系

不同隔离级别对并发异常的容忍度不同,如下表:

 
隔离级别脏读不可重复读幻读丢失更新(提交覆盖)
READ UNCOMMITTED✔️✔️✔️✔️
READ COMMITTED✔️✔️✔️
REPEATABLE READ(默认)✔️(需加锁)✔️(需加锁)
SERIALIZABLE

0voice · GitHub 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值