事务特性>

PS:不是所有的引擎都能支持事务,比如 MySQL 原生的 MyISAM 引擎就不支持事务,也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB

  • 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样,就好比买一件商品,购买成功时,则给商家付了钱,商品到手;购买失败时,则商品在商家手中,消费者的钱也没花出去。
  • 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的
  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
  • 一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。比如,用户 A 和用户 B 在银行分别有 800 元和 600 元,总共 1400 元,用户 A 给用户 B 转账 200 元,分为两个步骤,从 A 的账户扣除 200 元和对 B 的账户增加 200 元。一致性就是要求上述步骤操作后,最后的结果是用户 A 还有 600 元,用户 B 有 800 元,总共 1400 元,而不会出现用户 A 扣除了 200 元,但用户 B 未增加的情况

其他三个特性保证了一致性

并行事务引发的问题

  • 脏读:A改了但是回滚,B读到改了的数

  • 不可重复读:A读1,B更新为2提交,A再读变2

  • 幻读:A查1条,B插入一条提交,A再读变2

MySQL有以下隔离级别

  • 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到

  • 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;

    解决脏读问题

  • 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别

    解决脏读,不可重复读问题

  • 串行化(serializable);会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

    解决脏读,不可重复读,幻读问题

MySQL的隔离级别

MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了)

快照读

普通的select语句就是快照读

MVCC = Read View + Undo Log

Read View的结构:

image-20260513175206269

  • m_ids:启动了还没有提交的事务
  • min_trx_id:就是m_ids的最小值
  • max_trx_id:创建 Read View 时当前数据库中应该给下一个事务的 id 值(id是递增分配的)
  • creator_trx_id :指的是创建该 Read View 的事务的事务 id

可重复读-RR

可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View

每条记录修改时会形成这样一条版本链:

image-20260513185438045

事务读取的时候,会一直沿着版本链找到符合条件的:

  1. 版本trx_id 值小于 Read View 中的 min_trx_id;说明是创建View之前提交的,可见
  2. trx_id 值在 Read View 的 min_trx_idmax_trx_id 之间,就看在不在m_ids,在就说明该事务还活跃,就是还没提交,不可见;不在就说明已经提交,可见

PS:RR级别虽然解决的是不可重复读,但基本也能避免幻读

【即使中途有其他事务插入了新纪录,是查询不出来这条数据的,所以就很好地避免了幻读问题】

一例幻读的特殊例子:

image-20260513220259546

读已提交-RC

读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View

RC 下虽然每次查询都重新创建 Read View,但 MVCC 版本链的遍历规则与 RR 相同(都是比较 trx_idmin_trx_idmax_trx_idm_ids

其他隔离级别

读未提交:读不加任何锁

串行化:读隐式转换为 SELECT ... LOCK IN SHARE MODE

当前读

除了普通的select是快照读,其他都是当前读

  • update、insert、delete,这些语句执行前都会查询最新版本
  • select ... for update 这种查询语句读取最新数据

可重复读-RR

有间隙锁+临建锁,默认行锁为临键锁,基本避免避免幻读

一例幻读的例子:先快照读再当前读就会出现幻读

image-20260514120649155

读已提交-RC

  • 无间隙锁,因此不会产生间隙锁和临键锁,只有记录锁
  • 导致幻读可能发生

其他隔离级别

不说了没意思

两种隔离级别锁的一些情况

UPDATE和DELETE总是先当前读(Current Read),再对被匹配到的行加 X 锁

RC

1. 普通 SELECT

  • 无锁(快照读,每次查询基于最新 Read View)

2. SELECT … FOR UPDATE / LOCK IN SHARE MODE

  • 对查询命中记录行锁(排他或共享),无间隙锁
  • 如果使用唯一索引等值查询且记录存在 → 只锁该行。
  • 如果记录不存在 → 不加任何锁(因为没有行可锁)
  • 如果使用非唯一索引或范围查询 → 锁住所有满足条件的索引记录(但不会锁间隙)。

3. UPDATE / DELETE

  • 要修改/删除的记录行排他锁(X 锁)

  • 如果 WHERE 条件使用唯一索引精确匹配且记录存在 → 只锁该行

  • 如果 WHERE 条件使用非唯一索引或范围 → 锁住所有查询到的行(但扫描过程中若某行被其他事务锁住,会使用==半一致性读==:读取该行最新已提交版本,判断是否符合条件,若不符合则跳过该行,不等待锁;如果符合,就等待锁,等待结束后,会重新读取最新的已提交版本,再次判断)

    使用这种半一致性读,提高了并发性

  • 如果无索引 → 扫描全表,对每条记录尝试加 X 锁,但实际会通过半一致性读跳过不符合条件的行(仍可能锁较多行)

4. INSERT

  • 行排他锁(新记录)
  • 插入意向锁(一种间隙锁,但因为 RC 无其他间隙锁,所以插入意向锁几乎不会阻塞,仅用于防止与显式间隙锁冲突——而 RC 无显式间隙锁,故插入总是成功,除非唯一键冲突)

RR

隔离级别特性

  • 普通 SELECT 使用 MVCC事务内首次查询时创建 Read View 并复用,保证可重复读。
  • 当前读默认使用 临键锁(Next-Key Lock) = 记录锁 + 间隙锁,防止幻读(大部分场景)。
  • 唯一索引等值查询命中记录时,临键锁退化为 记录锁
  • 唯一索引等值查询未命中时,加 间隙锁
  • 范围查询或非唯一索引 → 临键锁。

1. 普通 SELECT

  • 无锁(快照读,复用事务内第一个 Read View)

2. SELECT … FOR UPDATE / LOCK IN SHARE MODE

加锁算法取决于索引类型查询值

image-20260514180652346

3. UPDATE / DELETE

  • 加锁规则SELECT ... FOR UPDATE 完全相同(因为都是当前读加 X 锁)。
  • 修改数据时,会对扫描到的索引范围加临键锁(或记录锁/间隙锁)。
  • 特别注意:即使 WHERE 条件只有一行,如果使用非唯一索引,也会加临键锁(锁住一个范围),可能导致其他事务插入相邻值被阻塞

4. INSERT

  • 行排他锁(新记录)。
  • 插入意向锁(在目标间隙上)。如果该间隙已被其他事务加了间隙锁或临键锁,则 INSERT 会等待(避免幻读)。
  • 唯一键冲突时,会加共享锁(S 锁) 在冲突的行上