MySQL中的事务
事物特性
- 原子性:事物时最小的执行单位,不允许分割。事物的原子性确保动作要么全部完成,要么完全不起作用,如果在执行过程中发生错误,会被回滚到事物开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:是指事物操作前和操作后,数据满足完整性约束,数据库保持一致性状态。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有有关完整的数据空间,对其他并发事务时隔离的。也就是说,消费者买商品这个事务,不影响其他消费者购买。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
InnoDB引擎通过什么技术来保证事务的这四个特性呢?
- 持久性是通过redo log(重做日志)来保证的;
- 原子性是通过undo log(回滚日志)来保证的;
- 隔离性是通过MVCC(多版本并发控制)或锁机制来保证的;
- 一致性则是通过持久性+原子性+隔离性来保证;
并行事务会引发什么问题?
MySQL服务端是允许多个客户端连接的,这意味着MySQL会出现同时处理多个事务的情况。
那么就可能会出现脏读、不可重复读、幻读的问题。
脏读
表示一个事务能够读取另一个事务中还未提交的数据。比如:某个事务尝试插入记录A,此时该事务还未提交,然后另一个事务尝试读取到了记录A。
事务 A 读取了事务 B 未提交的数据。若事务 B 回滚,事务 A 读到的数据就是无效的,A读到的就是过期的数据,这种现象被称为脏读。
不可重复读
在一个事务内多次读取同一个发数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了不可重复读的现象。
不可重复读的重点是修改:同样的条件,你读取过的数据,再次读取出来发现值不一样了。
脏读和不可重复读不一样的
简单来说,脏读是读到未提交数据,不可重复读是因其他事务提交修改导致同一事务内多次读结果不同。
幻读
在一个事务内多次查询某个符合查询条件的记录数量,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了幻读现象。
在一个事务中,按照相同的查询条件多次查询,由于其他事务对数据进行了插入或删除并提交,导致查询结果的行数发生了变化。
事务的隔离级别有哪些?
- 脏读:读到其他事务未提交的数据;
- 不可重复:读前后的数据不一致;
- 幻读:前后读取的记录数量不一致;
严重性排序如下:
脏读>不可重复读>幻读
SQL标准提出了四种隔离级别来规避这些想想,隔离级别越高,性能效率越低,这四个隔离级别别如下:
- 读未提交:指一个事务还没提交时,它做的变更就更能被其他事务看到;
- 读提交:指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读:指一个事务执行过程中看到的数据,一直跟这个事物启动时看到的数据是一致性的,MySQL InnoDB引擎的默认隔离级别;
- 串行化:会对记录加上读写锁,在多个事物对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事物执行完成,才能继续执行;
按隔离级别高低排序如下:
串行化>可重复读>读已提交>读未提交
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
也就是说:
-
在读未提交隔离级别下,可能发生脏读、不可重复和幻读现象;
-
在读提交隔离级别下,可能发生不可重复读和幻读现象。
-
在可重复读隔离级别下,可能发生幻读现象。
-
在串行化隔离级别下,都不会发生。
所以,要解决脏读,就要升级到读提交以上的隔离级别;要解决不可重复读现象,就要升级到可重复读级别等。解决幻读不建议将隔离级别升级到串行化。
MySQL虽然支持4种隔离级别,但是与SQL标准种规定的隔离级别允许发生的现象却有些出入。
MySQL InnoDB引擎的默认隔离级别虽然是重复读,但是它很大程度上避免幻读现象(并不是完全解决)解决方案有两种:
-
针对快照读(普通select语句):是通过MVCC方式解决幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致性的,即使中途有其他事务插入了一条数据,是查询不出来这条数据的,所以就很好的避免幻读问题。
-
针对当前读(select...for update等语句):是通过next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行select..for update语句的时候,会加上next-key lock,如果有其他事务在next-key lock锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以很好的避免幻读问题。
四种隔离级别如何实现
实现方式:
-
对于读未提交隔离级别来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
-
对于串行化隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
-
对于读提交和可重复读的隔离级别的事务来说,它们是通过Read View来实现的,它们的区别在于创建Read View的时机不同,可以把Read View理解成一个数据快照,定格在那一刻的状态。读提交隔离级别是每个语句执行前都会重新生成一个Read View,而可重复读隔离级别是启动事务时生成一个Read View,然后整个事务期间都在用这个Read View。