事务的特性
ACID
A
原子性:一个事务中的所有操作,要么全部完成,要么全部不完成。undo log保证
C
一致性:事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。原子性、隔离性、持久性保证
I
隔离性:多个并发事务交叉执行,使用相同的数据时,互不干扰,每个事务都有一个完整的数据空间。MVCC或锁机制保证
D
持久性:事务处理结束后,对数据的修改是永久的,即便系统故障也不会丢失。redo log保证
并行事务会引发什么问题
- 脏读:一个事务读到了,另一个未提交事务修改过的数据。
- 不可重复读:一个事务内多次读取同一个数据,前后两次读到的数据不一样。
- 幻读:一个事务内多次查询某个符合查询条件的记录数量,前后两次查询到的记录数量不一致。
事务的隔离级别有哪些
- 未提交读:
- 已提交读:MyISAM默认的隔离级别。彻底解决脏读
- 可重复读:InnoDB默认的隔离级别。彻底解决脏读、不可重复读(部分解决幻读)
- 串行化:彻底解决脏读、不可重复读、幻读
事务的隔离级别是如何实现的
- 未提交读:直接读取最新的数据。
- 串行化:加读写锁。
- 已提交读和可重复读:通过Read View来实现的。创建的时机不同。(已提交读:每个语句执行之前。可重复读:启动事务时。)
可重复读如何部分解决幻读
- 针对快照读
SELECT
:通过MVCC,事务执行过程中看到的数据,和事务启动时看到的数据时一致的。 - 针对当前读
SELECT...FOR UPDATE
:通过next-key lock(记录锁+间隙锁)阻塞其他事务的插入操作。
执行了“开始事务”就一定启动了事务吗?
不一定
- start/begin transaction 命令。之后执行第一条SELECE语句,才是事务真正启动的时机。
- start transaction with consistent snapshot命令。马上开启事务。
Read View 在MVCC里怎么工作?
Read View里的4个字段
creator_trx_id
:创建该Read View的事务的id
m_ids
:创建Read View时,当前数据库中活跃事务(启动了但还没提交的事务)的事务id列表。
min_trx_id
:m_ids的最小值。
max_trx_id
:全局事务中最大的事务id值+1。
聚簇索引记录中的两个隐藏列
trx_id
:事务id。
roll_pointer
:指向undo log中的旧版本记录。
trx_id与记录对事务可见性的关系
创建Read View后,trx_id的分类:
已提交事务
min_trx_id已启动但未提交的事务
(m_ids) max_trx_id还没有开始的事务
- trx_id小于min_trx_id,该版本的记录对当前事务可见。
- trx_id大于max_trx_id,不可见。
- trx_id在之间,在m_ids列表中,不可见。
- trx_id在之间,不在m_ids列表中,可见。
- trx_id和creator_trx_id相等,可见。
什么是MVCC?
通过版本链(事务的Read View里的字段和记录中的两个隐藏列的对比)来控制并发事务访问同一个记录时的行为。
可重复读是如何工作的
启动事务时生成一个Read View,整个事务期间都在用这个Read View。
读记录时判断trx_id,如果小于min_trx_id,或不在m_ids中,说明在生成Read View就已经提交修改了,可以读。否则,顺着roll_pointer在undo log中找,直到找到符合条件的trx_id。
已提交读是如何工作的
每次读取数据时,都会生成一个新的Read View。
MySQL可重复读隔离级别,完全解决幻读了吗
没有
幻读的2个例子:
- 对于快照读:事务A更新了一条事务B插入的记录x,那么事务A前后两次查询的记录条目就不一样了,就发生了幻读。(记录x的trx_id变成了事务A,对事务A就可见了)
- 对于当前读:先快照读,其他事务插入了一条记录,再当前读,前后两次查询的记录条目不一样,就发生了幻读。(锁加晚了)
如何避免:
在开启事务后,立马执行SELECT...FOR UPDATE
,对记录加锁,避免其他事务插入数据。