纸上得来终觉浅,绝知此事要躬行!
一 思考
- 事务是解决什么问题的?
- 为了解决这些问题,事务使用了哪些策略或手段?
- 脏读,幻读,不可重复读,概念理解
二 基础知识
1. 设置事务等级
- read uncommitted:小名等不及,总能看到最新数据,无论改动是否提交
- read commited:小名老实人,目不斜视,只看提交后的更新
- repeatable read:小名一根筋,它查看过的数据,每次查询结果不变,不管其它事务是否提交修改
- serializable:小名等等等,别人改了,它就等改动提交后再看;它看了,别人必须等它提交了再改
select @@tx_isolation;
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;
2. 数据操作
create database test;
use test;
create table person (
name varchar(30),
age int
);
insert into person values('xiaoerhei', 18);
update person set age=age+1 where name='xiaoerhei';
select * from person;
3. 查看数据库状态
show processlist;
show open tables from test;
三 并发场景梳理
事务级别
- ru : read uncommitted
- rc : read committed
- rr : repeatable read
- sz : serializable
事务组合: 在 2 个并发事务的情况下,共有 16 种不同并发事务的组合
[
(ru, ru), (ru, rc), (ru, rr), (ru, sz),
(rc, ru), (rc, rc), (rc, rr), (rc, sz),
(rr, ru), (rr, rc), (rr, rr), (rr, sz),
(sz, ru), (sz, rc), (sz, rr), (sz, sz)
]
一般情况下,业务接口的事务级别都是一样的,比如 spring 中进行事务管理时,相同的 service 接口,一般配置为相同的事务级别。
因此,[ (ru, ru), (rc, rc), (rr, rr), (sz, sz)] 这四种事务组合更常见。
事务流程说明:
- 事务名称: A,B
- $ : start transaction
- U : update/insert/delete
- Q : select
- R :rollback
- # : commit
设计时,事务的内容要尽量小而美,而不是大而全。事务越大,并发时流程的可能性越多,问题越复杂。下面仅列举一些典型的例子。
示例:
$(A, B) -> U(A) -> Q(B) -> #(A, B)
$(A, B) -> Q(A) -> U(B) -> #(A, B)
$(A, B) -> U(A) -> U(B) -> #(A, B)
$(A, B) -> Q(B) -> U(A) -> Q(B) -> #(A, B)
$(A, B) -> Q(B) -> U(A) -> #(A) -> Q(B) -> #(B)
$(A, B) -> Q(B) -> U(A) -> #(B) -> R(A) -> #(A)
$(A, B) -> U(A) -> U(B) -> R(A) -> #(B)
$(A) -> U(A) ->$(B) -> Q(B) -> #(A, B)
$(A) -> Q(A) ->$(B) -> U(B) -> #(A, B)
备注: $(A, B) -> U(A) -> Q(B) -> #(A, B) 的意思是,事务同时开启,事务 A 更新某数据 D,然后事务 B 查询 D 的信息,然后事务提交。
结果说明:
- SD : sql done, 执行 sql 完成;
- SB : sql block, 执行 sql 阻塞一定时间后完成;
- SE : sql error, 执行 sql 阻塞超时异常;
- DR : dirty read,脏读
- UR : unrepeatable read,不可重复读
- PR : phantom read,幻读
- OD : old data, 老数据
备注
sql 异常是否会引起事务终结并回滚,是可以配置的,SET XACT_ABORT = ON/OFF
四 示例分析
法无常法,因地制宜,要结合实际的应用场景去分析利弊,进而判断是否满足需求
scenario | (ru, ru) | (ru, rc) | (ru, rr) | (ru, sz) |
---|---|---|---|---|
$(A, B) -> U(A) -> Q(B) -> #(A, B) | ||||
$(A, B) -> Q(A) -> U(B) -> #(A, B) | ||||
$(A, B) -> U(A) -> U(B) -> #(A, B) | ||||
$(A, B) -> Q(B) -> U(A) -> Q(B) -> #(A, B) | ||||
$(A, B) -> Q(B) -> U(A) -> #(A) -> Q(B) -> #(B) | ||||
$(A, B) -> Q(B) -> U(A) -> #(B) -> R(A) -> #(A) | ||||
$(A, B) -> U(A) -> U(B) -> R(A) -> #(B) | ||||
(B) -> Q(B) -> #(A, B) | ||||
(B) -> U(B) -> #(A, B) |
1 (ru, rc)
$(A, B) -> U(A) -> Q(B) -> #(A, B)
- B 读取数据的实时性会受到影响。
- 尽管 A 已经修改了数据,但是 B 要等到 A commit 之后才能看到更新。
$(A, B) -> Q(B) -> U(A) -> #(A) -> Q(B) -> #(B)
- A 更新并提交后,B 再次查询被更新了的数据
- B 两次查询的结果不一致
$(A, B) -> U(A) -> U(B) -> #(A, B)
U(A)
insert into person values('sven', 30);
U(B)
update person set age=age+1;
- 这种情况下,sven 的年龄是不会被加 1 的。
- 如果 A,B 对同一数据进行更改时,B 会阻塞。
2 (ru, rr)
$(A, B) -> Q(B) -> U(A) -> #(A) -> Q(B) -> #(B)
- 尽管 A 已经提交了更新,B 两次查询的结果仍然一致
3 (ru, sz)
$(A, B) -> U(A) -> Q(B) -> #(A, B)
- 数据被 A 更新后,B 查询该数据时会阻塞,直到 A 事务提交
(未完待续 …)