MySQL事务
事务
事务特性
原子性(undo log回滚日志):要么全部完成,要么全部不完成
持久性(redo log重做日志):事务处理结束后,对数据的修改是永久的,即便系统故障也不会丢失
隔离性(MVCC多版本并发控制/锁机制):防止多个事务并发执行时由于交叉执行而导致数据的不一致
一致性(持久性+原子性+隔离性):指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态
脏读
一个事务读到了另一个未提交事务修改过的数据
不可重复读
在一个事务内多次读同个数据,出现前后两次读到数据不一样的情况
幻读
在一个事务内多次查询某个符合查询条件的「记录数量」,如果前后两次查询到的记录数量不一样,就意味着发生幻读
事务的隔离级别
- 读未提交:一个事务还没提交时,它做的变更就能被其他事务看到(可能发生:脏读、不可重复读、幻读)
- 读提交:一个事务提交后,它做的变更才能被其他事务看到(可能发生:不可重复读、幻读)
- 可重复读(InnoDB的默认隔离级别):一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的(可能发生:幻读)
- 串行化:会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完才能继续执行(会影响性能)
InnoDB避免幻读
- 针对快照读(普通 select 语句):MVCC 方式。因为可重复读隔离级别下,事务执行过程中看到的数据一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据,也查不出来这条数据
- 针对当前读(select … for update 等语句):记录锁+间隙锁。如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞
四种隔离级别如何实现
- 「读未提交」:因为可以读到未提交事务修改的数据,所以直接读最新数据就好了;
- 「串行化」:通过加读写锁的方式来避免并行访问;
- 「读提交」和「可重复读」:通过 Read View 实现,区别在于创建 Read View 时机不同(可把 Read View 理解成一个数据快照)「读提交」是在「每个语句执行前」都会重新生成一个Read View,而「可重复读」是「启动事务时」生成一个Read View,然后整个事务期间都在用这个Read View
MySQL开启事务命令
- begin/start transaction:执行命令后不代表事务启动,只有再执行第一条select语句,才是事务真正启动的时机
- start transaction with consistent snapshot :一执行就会马上启动事务
Read View四个字段
1、m_ids :创建 Read View 时当前数据库中「活跃事务」的事务id 列表(活跃事务指启动了但还没提交的事务)
2、min_trx_id :创建Read View时当前数据库「活跃事务」中事务 id 最小的事务(m_ids最小值)
3、max_trx_id :创建Read View时当前数据库中应该给下一个事务的id值(全局事务中最大的事务id+1)
4、creator_trx_id :创建该Read View事务的事务id
聚簇索引的两个隐藏列
1、trx_id:当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务id记在trx_id里
2、roll_pointer:每次对某条聚簇索引记录改动时都会把旧版本记录写入到undo log中。这个隐藏列是个指针,指向每个旧版本记录,于是就可以通过它找到修改前的记录
可重复读如何工作
可重复读是启动事务时生成一个Read View,然后整个事务期间都在用这个Read View
读提交如何工作
读提交隔离级别是在每次读取数据时,都会生成一个新的Read View
Read View在MVCC里如何工作?
MVCC:多版本并发控制,通过版本链来控制并发事务访问同一个记录
- 如果记录的trx_id<Read View中的 min_trx_id 值,表示这个版本的记录在创建Read View前已提交的事务生成的,所以该版本记录对当前事务可见
- 如果记录的trx_id>=Read View中的 max_trx_id 值,表示这个版本的记录是在创建Read View后才启动事务生成的,所以该版本的记录对当前事务不可见
- 如果记录的 trx_id 值在 Read View 的min_trx_id和max_trx_id之间,需要判断 trx_id 是否在 m_ids 列表中:
- 在:表示生成该版本记录的活跃事务依然活跃着(还没提交事务),所以该版本的记录对当前事务不可见
- 不在:表示生成该版本记录的活跃事务已经被提交,所以该版本的记录对当前事务可见