事务
一个或者一组操作的最小逻辑单元,要么全部成功。要么全部失败。
务机制存在的目的就是无论我们的操作过程中是成功、失败、异常、或是受到干扰的情况下,事务都能保证我们数据最终的一致性
。
隔离性、原子性、持久性是达成一致性目标的手段
事务ACID特性分别是原子性、一致性、隔离性、持久性, 一致性是事务的最终追求的目标,隔离性、原子性、持久性是达成一致性目标的手段,根据的文章我们已经知道隔离性是通过锁机制来实现的。 而事务的原子性和持久性则是通过redo log 和undo log来保障的。
事务提交成功由redo log保证数据持久性,而事务可以进行回滚从而保证事务操作原子性则是通过undo log 来保证的。
undo log除了进行事务回滚的日志外还有一个作用,就是为数据库提供MVCC多版本数据读的功能。
redo log :持久性
undo log : 原子性
事务的特性 ACID
要实现事务的最终目的,需要几种机制组合才能实现,这几种机制就是事务的几个特性,分别原子性、隔离性、一致性、持久性
。
用一句话总结来总结这几个特性之间的关系,那就是: 一致性是事务的最终目的,而原子性、隔离性、持久性其实都是为了实现一致性的手段。
-
原子性 Atomicity
一个事务必须是一系列操作的最小单元,这系列操作的过程中,要么整个执行,要么整个回滚,不存在只执行了其中某一个或者某几个步骤。
-
隔离性 Isolation
隔离性是说两个事务的执行都是独立隔离开来的,事务之前不会相互影响。
-
持久性 Durability
持久性是指一旦事务成功提交后,只要修改的数据都会进行持久化,不会因为异常、宕机而造成数据错误或丢失。
-
一致性 Consistency
事务要保证数据库整体数据的完整性和业务的数据的一致性,事务成功提交整体数据修改,事务错误则回滚到数据回到原来的状态;
原子性由 Undo log
实现事务回滚操作保证。
持久性由 Redo log
进行保证。
隔离性由 Mysql 的隔离级别保证。
隔离级别
- 读未提交 【Read_UNCOMITTED】 : 一个事务还没提交时,它对数据的更改,就能被其他事务看到。
- 读提交 【READ_COMMITTED】: 事务提交后,它做的更改才会被其他事务看到。
- 可重复读 【REPEATABLE_READ】: 事务执行过程中看到的数据,和这个事务启动时看到的数据一致。 (mvcc)
- 串行化 【SERIALIZABLE】: 写会加写锁,读会加读锁,出现冲突时,后访问的事务必须等前一个事务执行完成,才能继续执行
用什么方式实现事务的隔离性
- 锁
- MVCC
后续都会详细讲解
脏写、脏读、重复读、幻读
如果事务没有隔离性,那会产生什么问题?
脏写、脏读、重复读、幻读
-
脏写
在事务并发的时候,一个事务可以修改另外一个正在进行中的事务的数据,这可能会导致一个写的事务会覆盖另外一个写的事务数据,这也就是脏写问题。
解决方法:加写锁、不加读锁
Read_UNCOMITTED 读未提交隔离级别下,读取数据不加锁,在事务A修改数据时加写锁,别的事务可以读取事务A中的数据(修改前、修改后),但是在事务A 没提交时,无法对事务A的数据进行修改,因为写锁没有释放。
-
脏读
在事务并发的时候,一个事务可以读取到另外一个正在进行中的事务数据,这产生了脏读问题。
解决方法:读加读锁、写加写锁、读锁写锁不能共存
在 READ_COMMITTED 读已提交隔离级别下,当事务对数据进行修改时,得先要对数据加写锁,当事务读取数据时,首先需要对数据加读锁。 因为写锁与读锁不能共存,所以在修改数据的时候,其它事务会因为无法成功加读锁而阻塞。所以一个事务就无法读取另外一个未完成事务所修改的数据了。
-
不可重复读
在事务并发的时候,一个事务里多次对同一个数据进行读取,但是读取到的结果是不一样的,这种问题称为不可重复读问题。
不是已经加了读锁了吗,按理说,加了读锁的话,数据是无法被修改的,那为什么会有不可重复读的问题?
不可重复读的核心问题是:在一个事务第一次读和第二次读数据的间隔中可以被另一个事务所修改。因为在读已提交的的事务隔离级别下,事务中每次数据读取结束后,事务未结束,会释放读锁。这时另一个事务就可以对该数据加写锁。导致前后读取数据不一致。
解决方法:锁 、 MVCC
REPEATABLE_READ **(可重复读)**的事务隔离级别下,一个事务对数据的读取加写锁,并且在事务提交之前不会释放该写锁。
事务中一直加读锁的方式可以解决这个问题,但是这样子有性能问题,在读数据时无法修改数据,在修改数据时无法读诗句。
Mysql InnoDB 中对于可重复读采用了 MVCC 的方式。使用MVCC后读取数据的时候不会加读锁,而是读取的历史版本数据。
REPEATABLE_READ **(可重复读)**MVCC保证了在一个事务里多次读取的数据历史版本是一致的,所以就无法看到最新修改的数据,这样也就保证了一个事务里多次读取到的数据肯定是一致的。
- 快照读:事务开始和结束都是同一个视图,所以实现了可重复读隔离级别
- 当前读:每次读取数据都要新建当前的视图,所以必须加锁实现可重复读隔离级别
- 幻读
前面是对于修改数据,幻读是对于插入数据。
在事务并发的时候,一个事务可以往另外一个正在读取的事务查询范围内插入新数据,导致另外一个事务在第二次查询数据里,要比前一次查询的数据要多,同样的SQL后面一次查询凭空多出了数据,像幻觉一样所以称为幻读。
解决方式:
- 设置事务隔离级别为SERIALIZABLE
在SERIALIZABLE事务隔离级别下,所有的事务都串行化执行,一个事务的执行必须等前面的事务结束,这样的话查询的时候就无法有其他事务插入新的数据,所以不会产生幻读问题。
- 加间隙锁
幻读问题的本质在于,没有对查询范围内的所有数据进 (包括不存在的数据)进行加锁,而导致改查询范围内可以被插入新的数据,所以使用间隙锁,对查询的范围进行加锁,此时新插入的数据的事务会因为无法加锁成功而阻塞,所以就避免了幻读。
select * from user where id>2
间隙锁会对id>2的空间加锁,所以此时我们另外一个事务插入ID为3 、4、6、7… 都会因为锁阻塞而无法成功。
Mysql 默认隔离级别为 RR 可重复读,使用 MVCC + 间隙锁的方式解决了幻读。
学习资料:
极客时间《Mysql 45 讲》
数据库系列