事务及事务隔离级别
innodb存储引擎支持事务,myisam不支持事务
事务内的操作要么全部成功,要么全部失败,中途有失败则回滚
事务的ACID:原子性,一致性,隔离性,持久性
事务隔离级别需要解决的问题:
脏读:读到其他事务未提交的事务
不可重复读:同一事务内,不同时刻读到的同一批数据可能不一样
幻读:对于插入来说的,事务A按照一定条件进行数据读取,期间事务B插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现事务B新插入的数据
事务隔离级别:
读未提交
读提交
可重复读(默认隔离级别)
串行化
从上至下隔离强度逐渐增强
读未提交:不加锁,连脏读问题都解决不了
读提交:解决了脏读,但是还有不可重复读的问题。因为可能某个事务查询期间,有另一个事务修改并提交了数据,则可能导致同一事务两次数据读到的不一样。
可重复读:事务不会读到其他事务对已有数据的修改,事务开始有什么数据,那么事务提交前的任意时刻,数据都是一样的。但是,对于事务新插入的数据是可以读到的,这也就是幻读的问题。但是需要强调的是,MySQL的可重复读其实解决了幻读的问题。
串行化:问题都解决了,但是将事务的执行变为了顺序查询,相当于当线程。
MySQL如何实现事务隔离级别?
读未提交:不加锁,可以理解为没有隔离。
串行化:读的时候加共享锁,写的时候加排他锁。
剩下的两个既要允许一定的并发,又要解决一些问题,因此比较复杂。
MVCC
MySQL采用MVCC(多版本并发控制)的方式,实现读提交和可重复读
对于一个快照来说,能读到哪些基于以下规则:
当前事务内的更新,可以读到
版本未提交,不能读到
版本已提交,但是在快照创建后提交的,不能读到。
版本已提交,且是在快照创建前提交的,可以读到。
对于快照读(select)
对于读提交,每次进行快照读时都会生成最新的Read View
对于可重复读,只有事务开始的第一个快照读会创建Read View
对于当前读(update,insert,delete)
行锁加间隙锁来解决,这个合并的锁叫做Next-Key锁
在修改的记录两边的区间加上间隙锁。
这里需要注意一个问题,加锁的过程要分为有索引和无索引两种情况,有索引时,直接通过索引找到数据行,直接加锁。对于没有索引的,会为这张表所有行加上锁,不满足条件的释放锁,留下满足条件的,会极大影响性能。
漏洞?:默认隔离级别(可重复读)
事务A select
事务B insert
事务B commit
事务A select(与第一次select的结果一样)
事务A update 所有行
事务A select(会发现多出了事务B insert 的行)
这算出现了幻读吗?
所以说MVCC并不能完美解决RR下的幻读问题??
一旦事务A的修改操作覆盖到了其他事务插入的“幻行”,那么在下次select的时候,也会把这行数据一起查出来。
要解释这个现象,需要深入理解MVCC
隐藏字段
DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。
DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息
DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。
Read View结构
low_limit_id:目前出现过的最大的事务ID+1,即下一个将被分配的事务ID
up_limit_id:活跃事务列表trx_ids中最小的事务ID,如果trx_ids为空,则up_limit_id 为 low_limit_id
trx_ids:Read View创建时其他未提交的活跃事务ID列表。
creator_trx_id:当前创建事务的ID,是一个递增的编号
比较算法
db_trx_id < up_limit_id || db_trx_id == creator_trx_id(显示)
如果数据事务ID小于read view中的最小活跃事务ID,则可以肯定该数据是在当前事务启之前就已经存在了的,所以可以显示。
或者数据的事务ID等于creator_trx_id ,那么说明这个数据就是当前事务自己生成的,自己生成的数据自己当然能看见,所以这种情况下此数据也是可以显示的。
db_trx_id >= low_limit_id(不显示)
如果数据事务ID大于read view 中的当前系统的最大事务ID,则说明该数据是在当前read view 创建之后才产生的,所以数据不显示。如果小于则进入下一个判断
db_trx_id是否在活跃事务(trx_ids)中
不存在:则说明read view产生的时候事务已经commit了,这种情况数据则可以显示。
已存在:则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的。
实际例子
对应的事务10003的实际运行的三次select(原始数据id=1,age=19)
那么这个叫出现了幻读吗?
快照读和当前读混用造成的异常,不能算是出现了幻读
当前读的效果就是要读取最新版本,实际上是把隔离级别从 repeatable-read 降级到了 read-committed,所以快照读和当前读混用不算幻读。
我们是不是得说这个例子说明了 repeatable-read 允许“不可重复读异常现象”?显然不能。对于不可重复读异常,repeatable-read 隔离级别是明确不允许的。这就说明了当前读和快照读混用带来的异常不能称为幻读。
总结:
MVCC能实现快照读的可重复读,不会出现不可重复读和幻读
间隙锁加行锁(或者说Next-Key锁),实现当前读的可重复读,也不会出现不可重复读和幻读
快照读和当前读混用造成的异常,不能算是出现了幻读