事务的隔离级别
事务(Transaction)的存在是要保证一组数据库的操作全部成功或全部失败。例如在转账的时候,简单的说存在多步操作,查询余额,自己账户做减法,对方账户做加法,这几步操作必须全部成功或者全部失败,并且在事务的执行过程中数据被锁住,不会让其他事务再对数据进行更改。
MySQL 是一个支持多引擎的数据库系统,并且事务都在引擎中实现。其中 MyISAM 引擎不支持事务,我们以 InnoDB 为例剖析事务的实现方式。
首先,事务有着四大特性(原子性,一致性,隔离性,持久性)
当数据库中同时存在多个事务并发执行时,可能出现脏读,不可重复读,幻读等问题,事务的隔离级别就是为了解决这些问题。
脏读:事务A与事务B同时执行,事务A读取到了事务B更改的数据,并且事务B未提交
不可重复读:前后读取的同一个数据不一致,事务A比较长,在第二次读取某数据时发现与第一次读取的值不同,原因是被其他事务更改了。
幻读:读取数据总量时,前后不一致
幻读与不可重复读的区别是,前者读数量后者读数据,前者需要锁表解决,后者锁行就行。
隔离级别
- 读未提交
在这个隔离级别下,一个事务即便没有提交,它所做的更改也能被其他事务看到 - 读提交
一个事务提交后才能被其他事务看到,但是这会存在不可重复读的问题,即某个事务第一次读时另一个事务未提交,第二次读同一个数据时另一个事务已经将其更改并提交了,此时读取到的值跟第一次读的不一致。 - 可重复读
在这个隔离级别下,读提交级别下的不可重复读问题被解决了,也就是一个事务从头至尾读某个数据都是一致的,即便其他事务对其更改并提交了。 - 串行化
对某行数据,写会加写锁,读会加读锁,只有读锁可以共享,其他情况都会被阻塞,直到拿到锁的事务释放锁为止。
举例:假设表 X 中有一行数据,初始值为1。
执行顺序 | 事务A | 事务B | 读未提交 | 读提交 | 可重复读 | 串行化 |
---|---|---|---|---|---|---|
1 | 启动事务 查询得到1 | 启动事务 | - | - | - | - |
2 | - | 查询得到1 | - | - | - | - |
3 | - | 将1改为2 | - | - | - | 事务B被阻塞 直到事务A提交才能执行 |
4 | 查询得到V1 | - | V1=2 | V1=1 | V1=1 | |
5 | - | 提交事务 | - | - | - | |
6 | 查询得到V2 | - | V2=2 | V2=2 | V2=1 | |
7 | 提交事务 | - | - | - | - | |
8 | 查询得到V3 | - | V3=2 | V3=2 | V3=2 |
事务隔离实现(多版本并发控制 MVCC)
事务的隔离是使用视图实现的,这里所说的视图并不是通常我们使用的视图,这里的视图是专门用于实现事务隔离的,属于 InnoDB 引擎使用。
事务在创建的同时,会附带创建一个视图,在事务执行期间都以视图为准。具体到隔离级别,每个隔离级别下,视图创建的时机不同。
读未提交:不创建视图,直接返回最新数据
读提交:每一句SQL执行时创建视图
可重复读:事务开始时创建视图
串行化:加锁实现
上面提到的视图实际上只是为了方便理解,隔离级别的实现是基于回滚日志的,即 undo log
。不同时刻创建的视图,会指向不同回滚节点,在读取某个数据时,会根据对应的视图将数据回滚到对应节点再返回。
例如一个数据初始值为 1,按顺序通过加一操作改成了 2,3,4,并且在初始值时事务 A 创建了 视图 A,在值为 2 时事务 B 创建了视图 B,在最新值时事务 C 创建了视图 C。不同视图得到的值不同,具体如图:
最后通过是否创建视图和创建视图的时机来实现不同的隔离级别。
事务使用注意事项
MySQL 隔离级别参数
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
可重复读应用场景
在数据库备份时,需要备份某一时刻的所有数据,若在读提交级别下,备份事务在备份的数据可能受到其他事务的提交所影响,而在可重复读级别下事务开始后所有数据均不会受到其他事务影响,从事务开始到结束前数据都保持在开始那一刻的状态。
事务启动
使用begin
或start transaction
语句显示启动事务,使用commit
语句提交事务,回滚语句是rollback
。
其中使用set autocommit = 0
语句会将当前连接线程的自动提交功能关闭,也就是说所有 SQL 语句不会自动提交,直到主动commit
。
通过显示语句启动事务,并且设置autocommit
为1
,同时可以使用commit work and chain
语句提交事务并且开始下一个事务。