目录
一、MySQL事务
Mysql事务(Transaction)用于保证数据的一致性,事务是在数据库管理系统中执行的一个逻辑操作单元,它是由一组列数据库操作组成的逻辑工作单元。
这一组操作要么全部成功,要么全部失败,不存在部分成功部分失败的情况
1.1 事务的特性(ACID)
Innodb支持事务,Myisam不支持事务
1.1.1 原子性(Atomicity)
一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态
原子性是通过 undo log(回滚日志) 来保证的;
1.1.2 一致性(Consistency)
是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。A给B转账,用户A扣200块,用户B一定增长200块。
一致性则是通过持久性+原子性+隔离性来保证;
1.1.3 隔离性(Isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的;
1.1.4 持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
在Innodb中,持久性是通过 redo log (重做日志)来保证的;
1.2 并行事务遇到的问题
1.2.1 脏读
如果一个事务「读到」了另一个「还未提交的事务修改过的数据」,就意味着发生了「脏读」现象。
如果在上面这种情况事务 A 发生了回滚,那么事务 B 刚才得到的数据就是过期的数据,这种现象就被称为脏读。
1.2.2 不可重复读
在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。
在这过程中如果事务 B 更新了这条数据,并提交了事务,那么当事务 A 再次读取该数据时,就会发现前后两次读到的数据是不一致的,这种现象就被称为不可重复读。
1.2.3 幻读
在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。
发现和前一次读到的记录数量不一样了,就感觉发生了幻觉一样,这种现象就被称为幻读。
严重性
1.3 事务的隔离级别
1.3.1 读未提交
一个事务还没提交时,它做的变更就能被其他事务看到;
在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象;
1.3.2 读已提交
一个事务提交之后,它做的变更才能被其他事务看到
在「读提交」隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象;
1.3.3 可重复读
指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
在「可重复读」隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象;
1.3.4 串行化
会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
在「串行化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发生。
隔离水平
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
实现方式
- 读未提交:因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
- 串行化:通过加读写锁的方式来避免并行访问;
- 读提交、可重复读:通过
Read View
来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。
1.4 MVCC
1.4.1 定义
MVCC 是 Multi-Version Concurrency Control 的缩写,意为 多版本并发控制。
这是一种数据库事务管理技术,主要用于处理并发读写操作,以提高数据库的并发性能和一致性。在 MySQL 中,MVCC 通过数据行的多个版本管理来实现并发控制。当一个事务在执行 SELECT 操作时,如果该行已经被另一个事务更新过,那么 InnoDB 存储引擎会提供之前版本的值给当前事务,这样在做查询的时候就不用等待另一个事务释放锁。
1.4.2 Read View 是如何工作的?
聚簇索引记录中的两个隐藏列:
对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:
- trx_id:当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;
- roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。
通过判断记录的 trx_id
与Read View中的min_trx_id
和max_trx_id
的大小来判断该版本的记录对当前事务可见不可见。也就实现了可重复读
和读已提交
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)
1.4.3 可重复读如何最大限度的避免幻读?
快照读
也叫普通读,读取的是记录数据的可见版本,不加锁,不加锁的普通select语句都是快照读,即不加锁的非阻塞读。
快照读的执行方式是生成ReadView,直接利用 MVCC 机制来进行读取,并不会对记录进行加锁。
简单的select操作(不包括 select … lock in share mode, select … for update)
当前读
(select … for update 等语句),通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select … for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
1.4.4 可重复读隔离级别出现幻读的场景
第一个发生幻读现象的场景:
第二个发生幻读现象的场景:
- 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 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。