一、什么是事务
一组数据库操作,执行后要么都成功,要么都失败。
二、事务的四大特性(ACID)
1、原子性(Atomicity)
对数据库的一系列的操作,要么都是成功,要么都是失败,不可能出现部分成功或者部分失败的情况。
InnoDB通过 undo log 来实现原子性。undo log 记录了数据修改之前的值(逻辑日志),一旦发生异常,可以用undo log来实现回滚操作。
2、一致性(Consistent)
数据库的完整性约束没有被破坏,事务执行前后都是合法的数据状态。数据库自身提供了一些约束:比如主键必须是唯一的,字段长度符合要求。用户自定义的完整性通常要在代码中控制。
3、隔离性(Isolation)
多个事务对表或数据行的并发操作,应该是透明的,互相不干扰的。
4、持久性(Durability)
对数据库的任意的操作(增、删、改),只要事务提交成功,结果就是永久性的,不会因为数据库掉电、 宕机、意外重启,又变回原来的状态。
持久性是通过redo log和double write buffer (双写缓冲)来实现的。操作数据时,先写到内存的buffer pool 里面,同时记录 redo log,如果在刷盘之前出现异常,重启后可以读取redo log的内容,写入到磁盘,保证数据的持久性。使用 redo log 恢复数据的前提是数据页未损坏,若数据页损坏了,需要通过双写缓冲保证持久性。
三、事务的开启与结束
(1)增、删、改语句自动开启事务,一条SQL一个事务。每个事务都有编号,编号是递增的整数。
(2)要把多条SQL放在一个事务里面,需要手动开启事务。
- 手动开启事务有两种方式:一种是用 begin,---种是用 start transaction。
- 结束事务也有两种方式:一种是回滚事务rollback,一种是提交事务commit。
InnoDB里面有一个控制是否开启自动提交事务的参数(分为session级别和global级别):
show variables like 'autocommit';
默认值是ON,即开启自动提交事务。若改成OFF,需要手动结束事务。
四、事务并发带来的问题
1、脏读(读未提交)
指在一个事务里,由于其他事务修改了数据(没提交事务)而导致前后两次读取数据不一致的情况。

2、不可重复读
第一个事务,读到了其他事务已提交的数据,导致前后两次读取数据内容不一致的情况。

3、幻读
第一个事务,读到了其他事务已提交的数据,导致前后两次读取数据数量不一致的情况。

不可重复读和幻读最大的区别在于:修改或者删除造成的读不一致叫做不可重复读 ,插入造成的读不一致叫做幻读。
五、事务隔离级别
- Read Uncommitted (未提交读) 一个事务可以读取到其他事务未提交的数据。会出现脏读,没有解决任何问题。
- Read Committed (已提交读) 一个事务只能读取到其他事务已提交的数据,不能读取到其他事务未提交的数据。解决了脏读的问题。
- Repeatable Read (可重复读) 同一个事务里面多次读取同样的数据结果是一样的。解决了不可重复读的问题。
- Serializable (串行化) 在这个隔离级别里面,所有的事务都是串行执行的,对数据的操作需要排队,不存在事务的并发操作。解决了所有(脏读、不可重复读、幻读)问题。
事务隔离级别设置:
set global transaction isolation level read uncommitted;
set global transaction isolation level read committed;
set global transaction isolation level repeatable read;
set global transaction isolation level serializable;
InnoDb事务隔离级别:

InnoDB在RR的级别就解决了幻读的问题,可重复读也是其默认使用的事务隔离级别。
六、读一致性的解决方案
1、LBCC
要保证前后两次读取数据一致,那就在读取数据时,锁定要操作的数据、不允许其他事务对其进行修改。这种方案叫做基于锁的并发控制Lock Based Concurrency Control (LBCC)。
2、MVCC
(1)MVCC原则
基于LBCC的方案在读取数据时要锁定数据,意味着不支持并发的读写操作。对于大多数读多写少的应用,非常影响效率。
基于此提供另一个解决方案,为了让一个事务前后两次读取的数据保持一致,在修改数据之前,创建相应数据的副本或快照 ,修改数据之后来读取这个数据的副本或快照。这种方案叫多版本并发控制Multi Version Concurrency Control (MVCC)。
MVCC的原则:一个事务只能看到第一次查询之前已经提交的事务的修改和本事务的修改,不能看见本事务第一次查询之后创建的事务(事务ID比我的事务ID大)的修改以及未提交的事务的修改。
MVCC的效果:可以查询到本事务开始之前已经存在的数据,即使它在后面被修改或者删除了。查不到本事务之后新增的数据。
(2)MVCC实践演示
InnoDB的事务都有编号,为不断递增的整数。InnoDB为每行记录都添加了两个隐藏字段:
- DB_TRX_ID:事务ID,数据在哪个事务插入或修改为新数据的,就记录为当前事务ID。
- DB_ROLL_PTR:回滚指针(示例中把它理解为删除版本号,实际并不准确,正确理解应该是指向前一个版本的指针,不过不影响我们理解MVCC)。
步骤1:第一个事务,初始化数据(检查初始数据)

此时数据的创建版本为当前事务ID (假设事务编号为1),删除版本为空

步骤2:第二个事务,执行第1次查询,读取到两条原始数据,此时事务ID为2
步骤3:第三个事务,插入数据

此时多了一条数据,它的创建版本号为当前事务编号3

步骤4:第二个事务,执行第2次查询


MVCC査找规则:只能査找创建时间小于等于当前事务ID的数据、和删除时间大于当前事务ID的行(或未删除),即不能查到第二个事务开始之后插入的数据。tom这条数据的事务ID大于2,因此不能被第二个事务查询到。
步骤5:第四个事务,删除数据,删除 id = 2 的这条记录

此时 id = 2 的这条记录,删除版本被记录为当前事务ID:4,其他数据不变

步骤6:第二个事务,执行第3次査询


MVCC査找规则:只能査找创建时间小于等于当前事务ID的数据、和删除时间大于当前事务ID的行(或未删除),即可以查到第二个事务开始之后删除的数据。huihui 这条数据依然可以查询到。
步骤7:第五个事务,执行更新操作,这个事务的事务ID是:5

此时的数据,旧数据的删除版本被记录为当前事务ID:5 (undo),产生了一条新数据,创建ID为当前事务ID:5

步骤8:第二个事务,执行第4次查询


MVCC査找规则:只能査找创建时间小于等于当前事务ID的数据、和删除时间大于当前事务ID的行(或未删除)。"盆鱼宴"创建版本大于2、查不出来,"qingshan"删除版本大于2,可以查出来。
3、MVCC原理
InnoDB中,一条数据的旧版本,存放在undo log。多次修改后,这些undo log会形成一个链条,叫做undo log链。DB_ROLL_PTR 是指向undo log链的指针。
为了判断各个事务的可见性情况,必须要有一个数据结构用来存储:本事务ID、活跃事务ID、当前系统最大事务ID等信息,这个数据结构是Read View (可见性视图),每个事务都维护一个自己的Read View。

- m_ids :生成ReadView时,当前系统中活跃的、读写事务的事务id列表。
- min_trx_id :生成ReadView时,当前系统中活跃的、读写事务中最小的事务id,也就是m_ids中的最小值。
- max_trx_id :生成ReadView时,系统中应该分配给下一个事务的id值。
- creator_trx_id :生成该ReadView的事务的事务id。
基于以上参数,事务判断可见性的规则如下:
- 数据版本的 trx_id = creator_trx_id,本事务修改,可以访问。
- 数据版本的 trx_id < min_trx_id (未提交事务的最小ID),说明这个版本在生成ReadView时,已经提交,可以访问。
- 数据版本的 trx_id > max_trx_id (下一个事务ID),这个版本是生成ReadView 之后才开启的事务所建立的,不能访问。
- 数据版本的 trx_id 在min_trx_id 和 max_trx_id之间,看是否在m_ids中。 如果在,不可以访问。如果不在,可以访问。
- 如果当前版本不可见,就找undo log链中的下一个版本。(上述示例中对DB_TRX_PTR的描述不够准确,真正的DB_TRX_PTR是由新版本指向旧版本)
