1. ACID

  • 原子性Atomicity:事务是一组不可分割的单位,要么同时成功要么同时不成功
  • 一致性Consistency:事务前后的数据完整性应该保持一致(数据库的完整性:如果数据库在某个时间点下,所有的数据都符合所有的约束)
  • 隔离性Isolation:多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰。多个并发事务之间数据要相互隔离
  • 持久性Durability:一个事务一旦被提交,他对数据库中的数据的改变就是永久性的。即使数据库发生故障也不应该对其有任何影响

2. 事务的实现

事务的实现就是如何实现ACID特性,下面一图下概况下:
image.png

事务的实现通过 redo_log 和 undo_log, 以及锁实现,锁实现事务的隔离,需要了解锁的朋友请看这里,需要了解mysql的日志的朋友请看这里

redo_log 实现持久化和原子性,而undo_log实现一致性,二种日志均可以视为一种恢复操作,redo_log是恢复提交事务修改的页操作,而undo_log是回滚行记录到特定版本。二者记录的内容也不同,redo_log是物理日志,记录页的物理修改操作,而undo_log是逻辑日志,根据每行记录进行记录。

3. 事务控制语句

image.png

上图列出了事务的一些控制语句,start transaction/begin、commit、rollback相信大家都有用过。
savepoint identifier 可以创建事务的一个保存点,执行回滚操作时可以回滚到指定保存点,不需要回滚整个事务。
打个比例,假设你去旅游到目标地需要三个行程,第一程 深圳到广州高铁,第二程 从广州飞到雅加达,第三程 雅加达飞到某岛。 如果再第三程 飞机取消行程,事务要回滚,如果要你再会深圳,你肯定会心理一万个草泥马。因为再进入事务,第一步和第二步是不变的,所以不需要回滚,直接回滚第三步即可。

4. 事务的基础-MVCC

MVCC的内容可以参考这个:数据库MVCC详解

5. 事务隔离级别

image.png

隔离级别越低,则事务请求的锁和保持锁的时间就越短。

5.1 READ-UNCOMMITTED

未提交读,即一个事务读到了另一个未提交事务修改过的数据,整个过程如下图:
image.png

如上图,SessionA和SessionB分别开启一个事务,SessionB中的事务先将id为1的记录的name列更新为’lisi’,然后Session 中的事务再去查询这条id为1的记录,那么在未提交读的隔离级别下,查询结果由’zhangsan’变成了’lisi’,也就是说某个事务读到了另一个未提交事务修改过的记录。但是如果SessionB中的事务稍后进行了回滚,那么SessionA中的事务相当于读到了一个不存在的数据,这种现象也称为脏读

5.2 READ COMMITTED

已提交读,或者叫不可重复读。即一个事务能读到另一个已经提交事务修改后的数据,如果其他事务均对该数据进行修改并提交,该事务也能查询到最新值。如下图:
image.png

在第4步 SessionB 修改后,如果未提交,SessionA是读不到,但SessionB一旦提交后,SessionA即可读到SessionB修改的内容。

5.3 REPEATABLE READ

可重复读,即事务能读到另一个已经提交的事务修改过的数据,但是第一次读过某条记录后,即使后面其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值,而不是每次都读到不同的数据。如下图:
image.png

InnoDB默认是这种隔离级别,SessionB无论怎么修改id=1的值,SessionA读到依然是自己开启事务第一次读到的内容。

5.4 SERIALIZABLE

串行化, 上面三种隔离级别可以进行 读-读 或者 读-写、写-读三种并发操作,而SERIALIZABLE不允许读-写,写-读的并发操作。 如下图:
image.png

SessionB 对 id=1 进行修改的时候,SessionA 读取id=1则需要等待 SessionB 提交事务。可以理解SessionB在更新的时候加了X锁。

MySQL InnoDB 引擎 RR 隔离级别是否解决了幻读

引用一个 github 上面的评论:

Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读。

A事务先select,B事务insert确实会加一个gap锁,但是如果B事务commit,这个gap锁就会释放(释放后A事务可以随意dml操作),A事务再select出来的结果在MVCC下还和第一次select一样,接着A事务不加条件地update,这个update会作用在所有行上(包括B事务新加的),A事务再次select就会出现B事务中的新行,并且这个新行已经被update修改了,实测在RR级别下确实如此,事务中一旦使用的当前读,那么整个事务都是在当前读下进行的操作。

如果这样理解的话,Mysql的RR级别确实防不住幻读

有道友回复:

在快照读读情况下,mysql通过mvcc来避免幻读。

在当前读读情况下,mysql通过next-key来避免幻读。

select * from t where a=1;属于快照读

select * from t where a=1 lock in share mode;属于当前读

不能把快照读和当前读得到的结果不一样这种情况认为是幻读,这是两种不同的使用。所以我认为mysql的RR级别是解决了幻读的。

参考
深入理解事务的来龙去脉
MySQL事务原理