事务是由MySQL的引擎来实现的,MySQL默认的执行引擎InnoDB是支持事务的,但是MyISAM不支持事务。
MySQL事务
- 事务的特性
- 并行事务会引发什么问题?
- 脏读
- 不可重复读
- 幻读
- 隔离级别
- 如何实现四种隔离级别?
事务的特性
- 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束,如果事务在执行过程中发生错误,会被回滚到事务开始前的状态。
- 一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。
- 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
并行事务会引发什么问题?
数据库并发场景有三种,分别为:
- 读-读:不存在任何问题,也不需要并发控制
- 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,不可重复读,幻读
- 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
脏读
如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。
不可重复读
在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。
幻读
在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。
隔离级别
SQL 标准提出了四种隔离级别来规避上述现象:
- 读未提交(read uncommitted),指一个事务还没提交时,它做的变更就能被其他事务看到;
- 读已提交(read committed),指一个事务提交之后,它做的变更才能被其他事务看到;
- 可重复读(repeatable read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
- 串行化(serializable );会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;
也就是说,要解决脏读现象,就要升级到「读提交」以上的隔离级别;要解决不可重复读现象,就要升级到「可重复读」的隔离级别。
不过,要解决幻读现象不建议将隔离级别升级到「串行化」,因为这样会导致数据库在并发事务时性能很差。
InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它通过next-key lock 锁(行锁和间隙锁的组合)来锁住记录之间的“间隙”和记录本身,防止其他事务在这个记录之间插入新的记录,这样就避免了幻读现象。
如何实现四种隔离级别?
- 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
- 对于「读已提交」和「可重复读」隔离级别的事务来说,它们是通过 Read View 来实现的。
区别在于创建 Read View 的时机不同,可以把 Read View 理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View。 - 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:
- 第一种:begin/start transaction 命令;
- 第二种:start transaction with consistent snapshot 命令;
这两种开启事务的命令,事务的启动时机是不同的: - 执行了 begin/start transaction 命令后,并不代表事务启动了。只有在执行这个命令后,执行了增删查改操作的 SQL 语句,才是事务真正启动的时机;
- 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。