Skip to content

数据库事务隔离级别对比表

隔离级别 (Isolation Level)脏读 (Dirty Read)不可重复读 (Non-Repeatable Read)幻读 (Phantom Read)锁机制概述 (典型实现)性能 & 并发性典型适用场景
读未提交可能发生可能发生可能发生通常无读锁 (或读时不加锁/共享锁立即释放)最高极少使用。对数据一致性要求极低,只追求最高速度的场景。
读已提交不会发生可能发生可能发生写操作加排他锁,读操作使用快照读或短时共享锁 (读取后立即释放)大多数数据库的默认级别。对脏读零容忍,接受不可重复读和幻读的场景。如:报表查询、大多数OLTP应用。
可重复读不会发生不会发生可能发生写操作加排他锁,读操作加共享锁并在事务结束前持有 (范围锁可能用于防止幻读)中等需要保证同一事务内多次读取相同记录结果一致的场景。可接受可能的幻读。MySQL的默认级别。
可串行化不会发生不会发生不会发生最严格的锁机制 (范围锁、表锁等,通常基于锁或乐观并发控制)最低 (阻塞最多)要求最高数据一致性,绝对不允许任何并发问题的场景。如:银行核心交易、对账等。性能要求不高。

关键概念解释:

  1. 脏读 (Dirty Read): 一个事务读取了另一个未提交事务修改的数据。如果那个事务回滚,则第一个事务读到的数据就是无效的(“脏”数据)。
  2. 不可重复读 (Non-Repeatable Read): 在同一个事务中,两次读取同一条记录,得到了不同的结果。这是因为在两次读取之间,另一个已提交的事务修改了该记录。
  3. 幻读 (Phantom Read): 在同一个事务中,两次执行相同的查询(通常是范围查询),得到了不同的结果集(多出或少了记录)。这是因为在两次查询之间,另一个已提交的事务插入或删除了符合查询条件的记录。
  4. 锁机制 (Locking):
    • 共享锁 (S Lock / Read Lock): 允许其他事务读取,但不允许修改加了锁的数据。
    • 排他锁 (X Lock / Write Lock): 禁止其他事务读取或修改加了锁的数据。
    • 范围锁 (Range Lock): 锁定一个范围内的记录或索引间隙,主要用于防止幻读(在Repeatable Read或Serializable级别)。
  5. 快照读 (Snapshot Read): 一些数据库(如Oracle, PostgreSQL, SQL Server的Read Committed Snapshot)使用多版本并发控制 (MVCC),在Read Committed或更高隔离级别下,读操作访问的是事务开始时的数据快照,避免了脏读,并允许读操作不阻塞写操作(写操作仍可能阻塞其他写操作)。

幻读

官方文档: https://dev.mysql.com/doc/refman/8.0/en/innodb-next-key-locking.html

定义: 幻读发生在一个事务执行过程中,另一个事务向正在被读取的记录集中添加或删除了行。

举例:

  1. 建表语句
sql
CREATE TABLE `employees` (
  `id` int NOT NULL,
  `name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `salary` decimal(10,2) DEFAULT NULL,
  `department` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
  1. 测试数据
sql
INSERT INTO employees VALUES
(1, '张三', 5000.00, '销售'),
(2, '李四', 6000.00, '技术'),
(3, '王五', 7000.00, '技术'),
(5, '赵六', 8000.00, '管理');


-- 设置隔离级别为REPEATABLE READ(MySQL默认)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;



-- 第一次查询,返回2条记录
SELECT * FROM employees WHERE department = '技术';
-- 结果: id=2(李四), id=3(王五)

事务B:
-- 插入一条新记录,部门为"技术"
INSERT INTO employees VALUES (4, '钱七', 6500.00, '技术');
COMMIT;

-- 第二次查询,在REPEATABLE READ隔离级别下
-- 由于MySQL的MVCC机制,仍然只看到最初的数据
SELECT * FROM employees WHERE department = '技术';
-- 结果: id=2(李四), id=3(王五) - 没有看到id=4的钱七


-- 尝试更新技术部门的所有员工,加薪500元
UPDATE employees SET salary = salary + 500 WHERE department = '技术';


-- 第三次查询,在REPEATABLE READ隔离级别下
SELECT * FROM employees WHERE department = '技术';

在事务A第三次查询时,可以观测到3条数据。

原因

  1. Read View 不变:事务 A 的三次 SELECT 都使用第一次查询时创建的同一个 Read View (RV1)。快照没有改变。
  2. 数据版本变化
    • 初始状态:id=2 和 id=3 的 DB_TRX_ID=100 (对 RV1 可见)。id=4 的 DB_TRX_ID=201 (对 RV1 不可见)。
    • 事务 A 的 UPDATE 操作:
      • 修改了 id=2 和 id=3:将它们的 DB_TRX_ID 更新为 200
      • 修改了 id=4:将其 DB_TRX_ID 更新为 200
  3. 可见性规则应用
    • 对于 DB_TRX_ID = 200 (等于 creator_trx_id),无论之前的历史如何,对当前事务总是可见
    • 因此,在第三次查询时,id=2, id=3, id=4 的最新版本都标记为 TRX_A=200 修改,对事务 A 自身完全可见。

Released under the MIT License.