原创

MySQL事务隔离级别与一致性机制

事务与一致性基础

在现代数据库系统中,事务(Transaction)是保证数据可靠性与一致性的核心机制。事务是数据库管理系统提供的一种逻辑执行单元,它由一组操作组成,这些操作要么全部成功执行,要么全部失败回滚,从而保证系统状态的一致性。

事务最早由数据库理论中的 ACID 特性定义:

  • Atomicity(原子性)
  • Consistency(一致性)
  • Isolation(隔离性)
  • Durability(持久性)

其中,一致性与隔离性之间存在密切关系。数据库通过 事务隔离级别(Isolation Level) 来平衡并发性能与数据一致性。

在高并发系统中,如果多个事务同时访问同一数据,可能产生各种并发问题,例如:

  • 脏读(Dirty Read)
  • 不可重复读(Non-repeatable Read)
  • 幻读(Phantom Read)

为了解决这些问题,数据库引入了 事务隔离级别


ACID 中的一致性含义

一致性(Consistency)指的是:

事务执行前后,数据库必须从一个一致状态转移到另一个一致状态。

所谓一致状态,是指数据库满足所有约束条件,例如:

  • 主键约束
  • 外键约束
  • 唯一约束
  • 业务逻辑约束

例如银行转账:

A 账户:1000
B 账户:1000

A 向 B 转账 200。

事务执行:

A = A - 200
B = B + 200

事务执行前:

总金额 = 2000

事务执行后:

总金额 = 2000

如果事务执行到一半系统崩溃:

A = 800
B = 1000

总金额变为:

1800

此时数据库处于 不一致状态

因此数据库必须保证:

  • 要么全部执行
  • 要么全部回滚

这就是 原子性保证一致性

但在并发环境中,仅靠原子性无法解决问题,还需要 隔离性


并发事务产生的问题

为了理解隔离级别,必须先理解并发事务可能出现的问题。

假设存在如下表:

CREATE TABLE account (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50),
    balance DECIMAL(10,2)
) ENGINE=InnoDB;

初始化数据:

INSERT INTO account(name,balance) VALUES
('Alice',1000),
('Bob',1000);

脏读(Dirty Read)

脏读是指:

一个事务读取了另一个事务 尚未提交的数据

示例:

事务 A:

START TRANSACTION;

UPDATE account
SET balance = 500
WHERE name='Alice';

事务 B:

SELECT balance FROM account WHERE name='Alice';

事务 B 读取到:

500

如果事务 A 回滚:

ROLLBACK;

数据库实际数据仍然是:

1000

事务 B 读取的数据 从未存在过

这种现象称为 脏读


不可重复读(Non-repeatable Read)

不可重复读是指:

同一个事务内多次读取同一行数据,结果不同。

事务 A:

START TRANSACTION;

SELECT balance FROM account WHERE name='Alice';

返回:

1000

事务 B:

UPDATE account
SET balance=800
WHERE name='Alice';

COMMIT;

事务 A 再次读取:

SELECT balance FROM account WHERE name='Alice';

返回:

800

同一个事务内读取同一行,结果不同。

这就是 不可重复读


幻读(Phantom Read)

幻读是指:

同一事务中两次执行范围查询,返回的记录数量不同。

假设存在订单表:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT,
    amount DECIMAL(10,2)
) ENGINE=InnoDB;

事务 A:

START TRANSACTION;

SELECT * FROM orders WHERE amount > 100;

返回:

2 条记录

事务 B:

INSERT INTO orders(user_id,amount)
VALUES (1,200);

COMMIT;

事务 A 再次查询:

SELECT * FROM orders WHERE amount > 100;

返回:

3 条记录

突然多出一条数据,就像出现“幻影”。

这就是 幻读


SQL 标准事务隔离级别

SQL-92 标准定义了四种事务隔离级别。

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

隔离级别越高:

  • 数据一致性越强
  • 并发性能越低

数据库系统通常在 一致性与性能之间做权衡


MySQL 事务隔离级别

MySQL InnoDB 默认隔离级别是:

REPEATABLE READ

查询当前隔离级别:

SELECT @@transaction_isolation;

设置隔离级别:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

或者:

SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

READ UNCOMMITTED

READ UNCOMMITTED 是最低隔离级别。

特点:

  • 允许读取未提交数据
  • 可能发生脏读

事务执行过程中不进行严格隔离。

优点:

  • 并发性能最好

缺点:

  • 数据一致性最差

这种级别在实际生产环境中几乎不会使用。


READ COMMITTED

READ COMMITTED 是多数数据库默认隔离级别。

例如:

  • Oracle
  • PostgreSQL
  • SQL Server

特点:

  • 只能读取已提交数据
  • 避免脏读
  • 可能发生不可重复读

执行机制:

每次读取数据时,都会读取 最新已提交版本

示例:

事务 A:

START TRANSACTION;

SELECT balance FROM account WHERE id=1;

事务 B 更新并提交:

UPDATE account
SET balance=800
WHERE id=1;

COMMIT;

事务 A 再次读取:

SELECT balance FROM account WHERE id=1;

结果不同。


REPEATABLE READ

REPEATABLE READ 的目标是:

保证事务内多次读取同一行数据结果一致。

MySQL InnoDB 默认使用该隔离级别。

特点:

  • 不会发生脏读
  • 不会发生不可重复读
  • 理论上可能幻读

但是 MySQL InnoDB 实际上通过 Next-Key Lock 解决了幻读问题


MVCC 多版本并发控制

MySQL InnoDB 实现高并发的重要机制是:

MVCC(Multi-Version Concurrency Control)

MVCC 的核心思想:

同一数据行维护多个版本,通过版本号控制可见性。

每一行数据包含隐藏字段:

  • DB_TRX_ID(事务 ID)
  • DB_ROLL_PTR(回滚指针)

通过 undo log 维护历史版本。

示意:

row1
 ├─ version1
 ├─ version2
 └─ version3

读取时:

  • 根据事务 ID 判断可见版本
  • 不需要加锁

优点:

  • 提高并发能力
  • 减少锁竞争

Read View 机制

MVCC 通过 Read View 决定数据可见性。

Read View 包含:

  • 当前活跃事务列表
  • 最小事务 ID
  • 最大事务 ID

判断规则:

如果数据版本:

  • 小于最小事务 ID → 可见
  • 大于最大事务 ID → 不可见
  • 在活跃事务列表 → 不可见

通过这种方式保证:

  • 事务读取一致快照

当前读与快照读

MySQL 将读取分为两种类型:

快照读

普通查询:

SELECT * FROM account;

使用 MVCC。

特点:

  • 不加锁
  • 读取历史版本

当前读

需要获取最新数据并加锁:

SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
UPDATE
DELETE
INSERT

当前读会加锁,保证数据一致性。


InnoDB 锁机制

InnoDB 使用多种锁保证事务隔离。

主要包括:

  • 行锁(Row Lock)
  • 间隙锁(Gap Lock)
  • 临键锁(Next-Key Lock)

行锁

锁定某一行记录:

UPDATE account
SET balance=100
WHERE id=1;

只锁定 id=1 的记录。


Gap Lock

锁定索引区间。

例如:

SELECT * FROM account
WHERE id BETWEEN 10 AND 20
FOR UPDATE;

锁定:

(10,20)

防止插入新数据。


Next-Key Lock

Next-Key Lock 是:

行锁 + Gap Lock

作用:

  • 防止幻读

例如:

SELECT * FROM account
WHERE id=10
FOR UPDATE;

锁定:

(9,10]

SERIALIZABLE

SERIALIZABLE 是最高隔离级别。

特点:

  • 所有事务串行执行
  • 不会出现任何并发问题

数据库实现方式:

  • 对查询加共享锁
  • 对写操作加排他锁

优点:

  • 完全一致性

缺点:

  • 并发能力极低
  • 性能开销巨大

因此在实际生产系统中极少使用。


不同数据库默认隔离级别

数据库 默认隔离级别
MySQL InnoDB REPEATABLE READ
Oracle READ COMMITTED
PostgreSQL READ COMMITTED
SQL Server READ COMMITTED

MySQL 选择更高隔离级别的原因:

  • 结合 MVCC
  • 通过 Next-Key Lock 解决幻读

因此性能损失较小。


事务隔离级别的实际选择

在实际系统设计中,需要根据业务特点选择隔离级别。

场景 推荐隔离级别
高并发读系统 READ COMMITTED
金融系统 REPEATABLE READ
强一致系统 SERIALIZABLE

例如:

电商系统:

  • 订单查询 → READ COMMITTED
  • 库存扣减 → REPEATABLE READ

通过合理设计:

  • 保证数据一致性
  • 提高系统吞吐量

Spring 事务隔离级别

在 Spring 中,可以通过 @Transactional 指定隔离级别。

示例:

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer() {
    // 业务逻辑
}

Spring 支持以下隔离级别:

Isolation.DEFAULT
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE

DEFAULT 表示使用数据库默认隔离级别。


分布式系统中的一致性

在微服务架构中,单数据库事务无法覆盖多个服务。

此时需要使用:

  • 分布式事务
  • 最终一致性

常见解决方案:

2PC

两阶段提交。

问题:

  • 阻塞
  • 性能差

TCC

Try / Confirm / Cancel

特点:

  • 业务层控制事务

Saga

通过补偿事务实现一致性。

流程:

事务A → 事务B → 事务C
失败时反向补偿

总结

事务隔离级别是数据库并发控制的核心机制,通过不同隔离级别可以在 一致性与性能之间取得平衡

SQL 标准定义了四种隔离级别:

  • READ UNCOMMITTED
  • READ COMMITTED
  • REPEATABLE READ
  • SERIALIZABLE

MySQL InnoDB 默认使用 REPEATABLE READ,并结合 MVCC 与 Next-Key Lock 实现高并发与强一致性。

在实际系统设计中,需要根据业务需求选择合适隔离级别,同时在分布式系统中结合最终一致性策略,才能构建既高性能又可靠的系统架构。


正文到此结束
评论插件初始化中...
Loading...