快照读(普通select语句)通过MVCC方式解决幻读
读-写冲突
,做到即使有读写冲突时,也能做到 不加锁 , 非阻塞并发读
,而这个读指的就是 快照读(属于乐观锁)
, 而非 当前读 。当前读(select...for update等语句)通过next-key lock(记录锁+间隙锁)
当前读
实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。最新版本
(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁MySQL通过MVCC解决幻读,MVCC多版本并发控制结束,多版本通过undolog
实现,管理通过ReadView
实现
脏读和不可重复读
,如果从定义来看,它不能解决幻读,只能采取可串行化
乐观锁
方式解决幻读,所以MySQL在可重复读也解决了幻读问题。对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。
注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:
- 第一种:
begin/start transaction
命令;- 第二种:
start transaction with consistent snapshot
命令;这两种开启事务的命令,事务的启动时机是不同的:
- 执行了 begin/start transaction 命令后,并不代表事务启动了。只有在执行这个命令后,执行了增删查改操作的 SQL 语句,才是事务真正启动的时机;
- 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。
总结
:
快照
,也就是乐观锁
的实现方式,幻读不会出现,相当于解决了幻读,但是并不是编程串行化了,串行化读是通过加锁,一个一个的进行解决。UUID
:如果没有主键也没有唯一性索引,默认提供一个UUID隐藏字段trx_id
:记录最近一个更新事务的ID(当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里)roll_pointer
:回滚指针,是通过和undo log日志,旧版本写入到undo日志中,旧版本通过链表指针相连接,roll_point指向最近的undo log记录READ COMMITTED
和 REPEATABLE READ
隔离级别的事务,都必须保证读到 已经提交了的
事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题
creator_trx_id
,创建这个 Read View 的事务 ID。m_ids
,表示在生成ReadView时当前系统中「活跃且未提交」的事务id列表
。(“活跃事务”指的就是,启动了但还没提交的事务。)
min_trx_id
,创建 Read View 时,当前数据库中「活跃事务且未提交的」事务中最小事务的事务 idmax_trx_id
,表示生成ReadView时系统中应该分配给下一个事务的 id 值。max_trx_id 是系统最大的事务id值,这里要注意是系统中的事务id,需要区别于正在活跃的事务ID。
重点
)READ COMMITTD
、 REPEATABLE READ
这俩个隔离级别才有效,因为这俩个情况下考虑快照(读旧数据),而剩下的读为提交和串行化,读的都是最新数据ReadView
的原理, READ COMMITTD
、 REPEATABLE READ
这两个隔离级别的一个很大不同ReadView
的时机不同:
READ COMMITTD
在每一次进行普通SELECT
操作前都会生成一个ReadViewREPEATABLE READ
只在第一次进行普通SELECT
操作前生成一个ReadView
,之后的查询操作都重复ReadView
就好了。使用间隙锁,对范围加锁使得插入或者删除或者更新无法操作
场景一:
场景二:
要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select … for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。