<事务特性 -> 实现方式>
1.原子性 ->undo log
事务是一系列操作的集合,这些操作要么全部完成,要么全部不完成。如果在事务执行中发生错误,会被回滚到事务开始前的状态
2.一致性 ->原子性、持久性、隔离性
事务执行前后,数据库满足完整性约束,数据库保存一致状态
比如A用户给B用户转账,转账前后,他们的余额总和总是一致的
3.隔离性 ->MVCC / 锁机制
数据库允许多个并发的事务对其进行数据读写与修改,并且这些事务相互独立互不干扰
4.持久性 ->redo log
事务执行后,事务执行的结果会永久性地保存在磁盘中,即使系统故障也不会丢失
<为什么事务要有隔离级别?或者说,并发事务会引发什么问题?>
1.脏读:一个事务读到了另一个事务还未提交的事务修改过的数据。还未提交意味着随时可能回滚,那么读到的数据可能就是过期的数据
2.不可重复读:一个事务内多次读取同一个数据,发现前后两次读出的数据值不一样。
3.幻读:一个事务内多次查询某个条件下的记录数量,发现前后两次读出的记录数量不一样
<用四种隔离级别来规避这几种现象>
<不同隔离级别可能发生的现象不同>
要解决脏读问题 -> 升级到读已提交
要解决不可重复读问题 -> 升级到可重复读
要解决幻读问题 -> 不建议升级到串行化!!!(会大大降低事务的并发性)
Tip:虽然可重复读可能会发生幻读,但是它可以做到很大程度上的避免幻读,这也是InooDB存储引擎将它作为默认隔离级别的原因
详细内容:可重复读是否完全解决了幻读问题?
见我的另一篇文章:
Mysql默认的隔离级别“可重复读”是如何解决幻读问题的,完全解决了吗?
<事务隔离级别的具体实现>
具体区别:读提交在每次语句执行前生成一个ReadView,而可重复读在事务启动的时候生成一个ReadView,之后直到事务提交一直沿用这个ReadView
Tip:事务执行开启命令并不意味着就开启了事务
Mysql中有两种开启事务的命令:
1)begin/start transaction ->执行该命令后,只有执行了增删改查操作,事务才会启动
2)start transaction with consistent snapshot ->执行该命令后,事务就会启动
ReadView的四个字段:
聚集索引中的两个隐藏列:
那么ReadView的作用在哪呢?
并发进行的事务会让一条记录产生许多版本,ReadView的作用就是帮助我们判断这条记录的哪个版本是可用的
<可重复读RR的实现>
某个事务A在启动时创建自己的ReadView,并在事务提交前会一直沿用这个ReadView,ReadView中的m_ids中会有其它活跃事务的id,其它并发进行的事务比如事务B的id要么小于min_trx_id(已经提交),要么在A事务期间提交但是由于A事务的ReadView不会更新,B事务的trx_id始终会在m_ids中,所以不可以读到B事务修改的结果,这样就实现了可重复读的隔离级别
<读提交RC的实现>
某个事务A在读取数据的时候创建了自己的ReadView,并在每次做读取数据操作的时候都会更新这个ReadView,ReadView中的m_ids一开始会有其它并发的活跃事务比如事务B的trx_id,但是当事务B对数据进行修改并提交后,事务A再做读取操作时,创建的ReadView的m_ids中将没有事务B的trx_id在其中,而且,这个trx_id < min_trx_id(当然,也可能trx_id介于min_trx_id和max_trx_id之间,但是因为ReadView更新后,trx_id不在m_ids中了),所以是可以读到的,这样就实现了读提交的隔离级别
通过记录中的两个隐藏列(trx_id和roll_pointer)以及事务的ReadView中的字段的对比,来控制并发事务访问同一个记录的行为,这就是MVCC