事务特性
ACID
Atomicity原子性:事务中的操作要么全部完成,要么全部失败。
Consistency一致性:事务操作前后,数据满足完整性约束。
Isolation隔离性:允许并发执行事务,每个事务都有自己的数据空间,不会影响其他事务。
Durability持久性:事务操作完后,对数据的修改是永久的。
并行事务问题
1、更新丢失 – 两个事务同时对同一数据进行修改,导致某一个事务的修改的数据丢失。
事务撤销造成的撤销丢失:在事务A修改后(未提交),事务B修改时失败回滚,会同时把事务的修改也回滚了。
事务提交造成的覆盖丢失:事务A修改后(未提交),事务B修改成功,且提交,导致事务A的数据被覆盖。
2、脏读 – 事务执行中读取到了其他事务未提交的数据。
3、读已提交 – 事务读取到其他事务已经提交的数据。
4、不可重复读 – 同一事务中,多次读到的同一数据返回结果不一样。
5、可重复读 – 同一事务中,多次读到的同一数据返回结果一样。
6、幻读 – 在可重复读下,事务A查询某个数据不存在,然后这个时候事务B插入了这个数据,事务A想去插入这个数据时就报错,在可重复读情况下,事务A又去查询这个数据确实不存在。
- 不可重复读关注的是数据的变化: 一个事务内,相同的查询在不同时间点返回不同的数据。这可能是因为其他事务修改了数据,导致读取到的结果不一致。
- 幻读关注的是数据的新增或删除: 一个事务内,相同的查询在不同时间点返回了不同数量的行。这可能是因为其他事务在这个范围内插入或删除了数据,导致读取到的结果不一致。
事务隔离级别
SQL 标准定义了四个隔离级别:
- 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
- 串行化(serializable);会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
ReadView工作模式
ReadView中四个重要字段:
- m_ids:当前数据库中活跃事务(已开启但未提交的事务)id的列表。
- min_trx_id:活跃事务id列表中最小的事务id。
- max_trx_id:创建ReadView时,数据库给之后要开启事务的id值。
- creatot_trx_id:创建该ReadView的事务id。
聚簇索引中的两个隐藏列
- trx_id:保存当前记录由哪个事务产生或修改的。
- roll_pointer:每次对记录的改动都会把就版本的记录存入undo日志,该字段就是一个指向旧版本的指针。
判断记录对于某ReadView来说是否可见:
- 如果记录隐藏字段的trx_id < ReadView中的min_trx_id,说明记录是在ReadView创建前就有了,所以这条记录是可见的。
- 如果记录隐藏字段的trx_id >= ReadView中的max_trx_id,说明记录是在ReadView创建后产生的,所以这条记录是不可见的。
- 如果记录隐藏字段的trx_id 在min_trx_id和max_trx_id之间
- 情况一,trx_id在m_ids中,说明这条记录还没有提交,是不可见的。
- 情况二,trx_id不在m_ids中,说明这条记录已经提交,是可见的。
读已提交工作模式
怎么解决的读未提交?
在一个事务中每次查询操作都会生成一个ReadView。事务A读取到事务B未提交的数据,根据“判断记录对于某ReadView来说是否可见”中的3-情况一,可知事务A访问不到事务B未提交的数据,所以解决了读未提交。
可重复读工作模式
怎么解决的不可重复读?
在事务启动时生成一个ReadView,之后整个事务中都是同一个ReadView。
根据“判断记录对于某ReadView来说是否可见”中的2可知,对于其他事务已经提交的数据,在该事务中是不可见的。
可重复读未完全解决幻读
在可重复读模式下,普通的select语句是快照读。其他都是当前读。
快照读通过 MVCC 解决幻读,当前读通过 next-key lock 解决幻读。
但仍可能出现幻读场景
情况一:
- T1 时刻:事务 A 查询 id = 5 的值不存在
- T2 时刻:事务 B 插入一个 id = 5 的记录并提交;
- T3 时刻:事务 A 更新 id = 5 的记录。
- T4 时刻:事务 A 查询 id = 5 的记录,存在!
情况二
- T1 时刻:事务 A 先执行「快照读语句」:select * from t_test where id > 100 得到了 3 条记录。
- T2 时刻:事务 B 往插入一个 id= 200 的记录并提交;
- T3 时刻:事务 A 再执行「当前读语句」 select * from t_test where id > 100 for update 就会得到 4 条记录,此时也发生了幻读现象。
以上的解决方法是,在事务的一开始就使用 select … for update 执行当前读。