MySQL 事务知识梳理

148 阅读5分钟

知识点汇总

1 mysql 事务是在引擎层实现的,主要学习 InnoDB 的特定实现

2 事务的 ACID,(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)

3 当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

4 SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )

ps:隔离级别是针对读-写,写-读场景的,对于写-写场景需要用锁来考虑

  • 读未提交:B事务做出修改但是还没提交时,它做的变更就能被A事务看到

  • 读提交:B事务提交之后,它做的变更才会被A事务看到(解决脏读)

  • 可重复读:A事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。无论B事务如何修改和提交(解决脏读、不可重复读) 可重复读是否解决了幻读?可以说解决了,也可以说没解决:

例子如下,下面的例子是否是可重复读下出现幻读?见仁见智吧,反正事务B没有插入数据,但是却读到了事务A插入的数据,但是事务B不完全是读事务,做出了更新操作因此新数据就带有它的版本号,自然能看到:

ps: 网上说间隙锁能解决可重复读隔离级别下的幻读问题,这里的读实际上指的是加锁读,很多网友都搞混了加锁读和快照读的意义,此外当我选择更新 test = 2 的数据,在RR下这个范围就确定了,其它事务再插入一条 test = 2 的数据也会被锁住,所以在RR下用间隙锁解决加锁读的幻读问题是正确的,更新语句带 where 条件就可以理解为加锁读

ps: RC下不会加间隙锁,RR下会加间隙锁,假如表 C 存在如下数据 (5,5),(10,10),(15,15),事务A:update C set id = 66 where test = 10,事务B想要执行 insert into C values(6,6) 在RR下就会被锁住,RC则不会,因此再一次说明,MySQL在RR下用间隙锁解决的幻读问题,指的是加锁读,而不是快照读,这也导致了网上有人争论这个观点,因为他们根本没有说明快照读和加锁读的根本区别!

image.png

  • 串行化,顾名思义是对于同一行记录,“写” 会加 “写锁” ,“读” 会加 “读锁” 。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行(解决脏读、不可重复读、幻读)

举例说明:

mysql> create table T(c int) engine=InnoDB;
insert into T(c) values(1);

image.png

  • 读未提交: V1、V2、V3 都是 2
  • 读提交:V1 是 1,V2、V3 的值是 2
  • 可重复读:则 V1、V2 是 1,V3 是 2
  • 串行化:则在事务 B 执行 “将 1 改成 2” 的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2,同理,事务B执行 “将1改成2” 这个操作后若不提交事务,事务A执行 “查询得到值V3” 这步操作也会被锁住,读锁和读锁直接可以兼容,但是写锁互斥其它锁

上面的讨论都是AB两个事务都是同一个隔离级别(例如若B事务是串行化,A事务是可重复读,那么即使B事务锁住了某些数据,A事务不会阻塞,当然手动加锁,例如:select * from T lock in share mode 也会被锁住,若只是普通的语句,A事务和B一样是串行化才会被锁住)

事务到底是隔离还是不隔离的

ps: 普通的begin不会立刻创建快照

ps:任何隔离级别都不允许出现脏写问题

如下图: 假设 k 的初始值是 1 image.png 在可重复读的隔离级别下,事务B查询得到的结果是 k = 3,说明事务B “看到” 了事务 C 的更新,原因在于:更新数据都是先读后写的,而这个读,只能读当前的值,称为 “当前读” (current read)跟隔离级别无关,更新的时候总是会看到最新的数据。

除了 update 语句外,select 语句如果加锁,也是当前读。

如下图所示,在可重复读的隔离级别下,select后面加上lock in share mode 代表读最新值,显然违背了可重复读的语义,因此平常讨论的隔离级别都是普通读

image.png

小结

两个事务同时修改同一行数据,无论什么隔离级别都会加锁,隔离级别主要讨论:一个事务做修改,另一个事务能否读到的问题。

原理:redo_log(回滚日志) + MVCC(多版本并发控制),每一行数据有多个版本,所谓的版本号就是事务id,对于同一行数据,每个事务根据隔离级别看到不同的版本,因此读到不同的值

难点思考

下面会发生什么情况?

mysql> create table T(c int unique) engine=InnoDB;
insert into T(c) values(1),(2),(3);
A事务B事务
begin;begin;
update T set c = 4 where c = 3;
update T set c = 5 where c = 4;
commit;commit;

结论: B执行更新的时候会被锁住,where c = 3 或 c = 4 会被锁住,但是 where c = 其它值却不会,关键词:间隙锁

参考