课程 › 第 9 章 › 实验二
第 9 章 · 实验二 · 在第三节之后

事务的隔离级别

用两个窗口模拟两个并发的事务,按课件例 9-2 到例 9-4,比较四种隔离级别下读到的数据。

环 境

MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。

执 行

Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。

注 意

输入法切到英文。表名、字段名不加引号,字符串值才用单引号。

本次实验的顺序 后一个实验用到前一个的结果

  1. 实验 2-1查看和设置隔离级别
  2. 实验 2-2读未提交:读到脏数据
  3. 实验 2-3读已提交:不可重复读
  4. 实验 2-4可重复读
  5. 实验 2-5串行化
第一步

数据准备(必做)

与实验一相同的脚本,执行后三个账户回到初始余额。

整段复制,全部执行下载 .sql
-- 第 9 章实验数据:银行账户库 bankaccount 和账户表 account
-- 三次实验共用。整段执行一次即可,想从头再做时重新执行。
CREATE DATABASE IF NOT EXISTS bankaccount DEFAULT CHARACTER SET utf8mb4;
USE bankaccount;
DROP TABLE IF EXISTS account;
-- id 账户号,R 余额,datetime 最近一次操作的时间
CREATE TABLE account (
  id VARCHAR(20) NOT NULL,
  R DECIMAL(10,2) NOT NULL,
  datetime DATETIME,
  PRIMARY KEY (id)
);
INSERT INTO account VALUES
('account1', 500.00, '2026-09-01 09:00:00'),
('account2', 900.00, '2026-09-01 09:00:00'),
('account3', 900.00, '2026-09-01 09:00:00');
SELECT * FROM account;
2-1

查看和设置隔离级别

讲义 · 隔离级别
目的
查看当前会话的隔离级别,并练习设置隔离级别。
前提
已执行数据准备。
  1. 查看当前的隔离级别
  2. 把当前会话的隔离级别设为读已提交,再查看
  3. 改回可重复读

参考答案

可以直接复制到 Workbench 执行
SELECT @@transaction_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL
    READ COMMITTED;
SELECT @@transaction_isolation;
-- 改回 MySQL 的默认级别
SET SESSION TRANSACTION ISOLATION LEVEL
    REPEATABLE READ;
SELECT @@transaction_isolation;

语句解释

@@transaction_isolation当前会话的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL …只修改当前会话(当前窗口)的隔离级别
GLOBAL写 GLOBAL 时修改全局的级别,对以后新建的连接有效,本实验不用
执行后看:第一次查到 REPEATABLE-READ,是 MySQL 的默认级别。设置后变为 READ-COMMITTED,最后又改回 REPEATABLE-READ。
2-2

读未提交:读到脏数据

讲义 · 读未提交讲义 · 丢失更新与读脏数据
目的
按例 9-2,在读未提交级别下,读到另一个事务还没有提交的数据。
前提
已完成实验 2-1。

要用两个窗口:在 Workbench 首页再点一次同一个连接,会打开第二个连接标签页。两个标签页是两个独立的会话,下面称为窗口 A 和窗口 B,两个窗口都先执行 USE bankaccount;。每一步只执行这一步的语句:选中这几行,按 Ctrl+Shift+Enter。

  1. 窗口 A 设为读未提交,开始事务,查询余额
  2. 窗口 B 开始事务,给 account1 存入 100 元,不提交
  3. 窗口 A 再查一次
  4. 窗口 B 回滚,窗口 A 第三次查询后提交

参考答案 按步骤在两个窗口中执行

步骤窗口 B
1
窗口 A
SET SESSION TRANSACTION ISOLATION LEVEL
    READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM account;

account1 是 500

2
窗口 B
START TRANSACTION;
UPDATE account SET R = R + 100
WHERE id = 'account1';

不提交

3
窗口 A
SELECT * FROM account;

account1 是 600,读到了 B 没有提交的数据

4
窗口 B
ROLLBACK;

撤销存款

5
窗口 A
SELECT * FROM account;
COMMIT;

account1 又是 500,刚才读到的 600 是脏数据

语句解释

READ UNCOMMITTED读未提交,可以读到其他事务还没有提交的修改
脏数据B 回滚以后,A 读到的 600 从来没有真正存在过
执行后看:A 的三次查询分别是 500、600、500。
2-3

读已提交:不可重复读

讲义 · 读已提交
目的
按例 9-3,在读已提交级别下,同一个事务中两次读到的数据不同。
前提
已完成实验 2-2。两个窗口都开着。

两个窗口的使用方法见实验 2-2。

  1. 窗口 A 设为读已提交,开始事务,查询余额
  2. 窗口 B 开始事务,给 account1 存入 100 元,不提交
  3. 窗口 A 第二次查询
  4. 窗口 B 提交,窗口 A 第三次查询后提交

参考答案 按步骤在两个窗口中执行

步骤窗口 B
1
窗口 A
SET SESSION TRANSACTION ISOLATION LEVEL
    READ COMMITTED;
START TRANSACTION;
SELECT * FROM account;

account1 是 500

2
窗口 B
START TRANSACTION;
UPDATE account SET R = R + 100
WHERE id = 'account1';

不提交

3
窗口 A
SELECT * FROM account;

还是 500,读不到未提交的数据

4
窗口 B
COMMIT;
5
窗口 A
SELECT * FROM account;
COMMIT;

变为 600,同一个事务中两次读的结果不同

语句解释

READ COMMITTED读已提交,只能读到其他事务已经提交的数据
不可重复读在 A 的一个事务中,B 提交前后两次读到的值不同
执行后看:A 的三次查询分别是 500、500、600。避免了脏读,但出现了不可重复读。
2-4

可重复读

讲义 · 可重复读
目的
按例 9-4,在可重复读级别下,同一个事务中多次读到的数据相同。
前提
已完成实验 2-3,account1 现在是 600。

两个窗口的使用方法见实验 2-2。

  1. 窗口 A 设为可重复读,开始事务,查询余额
  2. 窗口 B 开始事务,给 account1 存入 100 元,在 B 中查询后提交
  3. 窗口 A 再查一次,提交后再查一次

参考答案 按步骤在两个窗口中执行

步骤窗口 B
1
窗口 A
SET SESSION TRANSACTION ISOLATION LEVEL
    REPEATABLE READ;
START TRANSACTION;
SELECT * FROM account;

account1 是 600

2
窗口 B
START TRANSACTION;
UPDATE account SET R = R + 100
WHERE id = 'account1';
SELECT * FROM account;
COMMIT;

B 自己查到 700,然后提交

3
窗口 A
SELECT * FROM account;

仍是 600

4
窗口 A
COMMIT;
SELECT * FROM account;

事务结束后再查,看到 700

语句解释

REPEATABLE READ可重复读,MySQL 的默认级别
A 第二次查询A 的事务中读的是事务开始后第一次读时的数据,B 提交的修改看不到
A 提交后新的查询不在原来的事务中,读到最新的数据
执行后看:A 在事务中两次都读到 600,提交后读到 700。
2-5

串行化

讲义 · 串行化
目的
在串行化级别下,一个事务查询过的数据,另一个事务要等它结束才能修改。
前提
已完成实验 2-4。

标着“等待”的一步,语句会一直处于执行状态。这时要在 20 秒内切到另一个窗口做下一步,否则 Workbench 会报超时。

  1. 窗口 A 设为串行化,开始事务,查询余额小于 1000 的账户
  2. 窗口 B 插入一个新账户 account4,余额 300
  3. 窗口 A 提交,看窗口 B 的情况
  4. 窗口 A 把隔离级别改回可重复读

参考答案 按步骤在两个窗口中执行

步骤窗口 B
1
窗口 A
SET SESSION TRANSACTION ISOLATION LEVEL
    SERIALIZABLE;
START TRANSACTION;
SELECT * FROM account WHERE R < 1000;

查到 3 个账户

2
窗口 B
INSERT INTO account VALUES ('account4', 300, NOW());

等待

3
窗口 A
COMMIT;

A 提交后,B 的插入马上完成

4
窗口 A
SET SESSION TRANSACTION ISOLATION LEVEL
    REPEATABLE READ;
SELECT * FROM account;

4 个账户

语句解释

SERIALIZABLE串行化,最高的隔离级别,查询时也会给读到的数据加锁
B 等待A 的事务结束前,B 不能插入会被 A 读到的新行,所以也不会出现幻象读
代价并发的事务只能排队执行,等待时间长
执行后看:B 的 INSERT 一直等待,A 提交后才完成。最后查到 4 个账户。
保山学院·人工智能教研室·曹鼎鼎