用两个窗口模拟两个并发的事务,按课件例 9-2 到例 9-4,比较四种隔离级别下读到的数据。
MySQL 8.0 和 MySQL Workbench,机房电脑已装好。所有语句在 Workbench 的 SQL 编辑区中执行。
Ctrl+Enter 执行光标所在的一句,Ctrl+Shift+Enter 执行全部。Mac 上把 Ctrl 换成 ⌘。
输入法切到英文。表名、字段名不加引号,字符串值才用单引号。
与实验一相同的脚本,执行后三个账户回到初始余额。
-- 第 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;
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 时修改全局的级别,对以后新建的连接有效,本实验不用 |
要用两个窗口:在 Workbench 首页再点一次同一个连接,会打开第二个连接标签页。两个标签页是两个独立的会话,下面称为窗口 A 和窗口 B,两个窗口都先执行 USE bankaccount;。每一步只执行这一步的语句:选中这几行,按 Ctrl+Shift+Enter。
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; SELECT * FROM account;
account1 是 500
START TRANSACTION; UPDATE account SET R = R + 100 WHERE id = 'account1';
不提交
SELECT * FROM account;
account1 是 600,读到了 B 没有提交的数据
ROLLBACK;撤销存款
SELECT * FROM account; COMMIT;
account1 又是 500,刚才读到的 600 是脏数据
| READ UNCOMMITTED | 读未提交,可以读到其他事务还没有提交的修改 |
| 脏数据 | B 回滚以后,A 读到的 600 从来没有真正存在过 |
两个窗口的使用方法见实验 2-2。
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; SELECT * FROM account;
account1 是 500
START TRANSACTION; UPDATE account SET R = R + 100 WHERE id = 'account1';
不提交
SELECT * FROM account;
还是 500,读不到未提交的数据
COMMIT;SELECT * FROM account; COMMIT;
变为 600,同一个事务中两次读的结果不同
| READ COMMITTED | 读已提交,只能读到其他事务已经提交的数据 |
| 不可重复读 | 在 A 的一个事务中,B 提交前后两次读到的值不同 |
两个窗口的使用方法见实验 2-2。
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; SELECT * FROM account;
account1 是 600
START TRANSACTION; UPDATE account SET R = R + 100 WHERE id = 'account1'; SELECT * FROM account; COMMIT;
B 自己查到 700,然后提交
SELECT * FROM account;
仍是 600
COMMIT; SELECT * FROM account;
事务结束后再查,看到 700
| REPEATABLE READ | 可重复读,MySQL 的默认级别 |
| A 第二次查询 | A 的事务中读的是事务开始后第一次读时的数据,B 提交的修改看不到 |
| A 提交后 | 新的查询不在原来的事务中,读到最新的数据 |
标着“等待”的一步,语句会一直处于执行状态。这时要在 20 秒内切到另一个窗口做下一步,否则 Workbench 会报超时。
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; SELECT * FROM account WHERE R < 1000;
查到 3 个账户
INSERT INTO account VALUES ('account4', 300, NOW());
等待
COMMIT;A 提交后,B 的插入马上完成
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; SELECT * FROM account;
4 个账户
| SERIALIZABLE | 串行化,最高的隔离级别,查询时也会给读到的数据加锁 |
| B 等待 | A 的事务结束前,B 不能插入会被 A 读到的新行,所以也不会出现幻象读 |
| 代价 | 并发的事务只能排队执行,等待时间长 |