MySQL 事务知识整理
最近在招 DBA ,在面试的过程中,通过学习也加深了对事务这一概念的理解,下面做个简单的整理。
1、什么是事务
事务是由一步或几步数据库操作序列组成逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。
事务是在引擎层实现的,在 MySQL 中只有使用了 Innodb 引擎的数据库或表才支持事务。MyISAM 引擎就不支持事务。
2、事务的作用
事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
事务用来管理 insert, update, delete 语句。
3、事务的 ACID 特性
原子性 Atomicity
又叫不可分割性,要么全完成,要么全不完成。就像原子是物质的最小组成单位一样,原子内的各种操作是一个整体。如果执行过程中发生了错误,就会回滚到最初状态。
一致性 Consistency
事务开始前和事务结束后,数据库的完整性不会被破坏,这表明操作的数据必须符合所有规范。
隔离性 Isolation
又叫独立性,数据库允许多个事务同时执行,隔离性可以防止多个事务并发执行导致数据的不一致。
持久性 Durability
事务处理结束后,对数据的修改是永久的,即使系统故障也不会丢失。
4、事务的隔离级别
下面的 4 种隔离级别,越往下,效率越低,并发越低,安全性越高。
1)读未提交(Read Uncommitte,RU)
- 定义:所有事务都可以看到其他未提交事务的执行结果。
- 说人话:一个事务里可以读到别人还没有提交的变更。别人可能处理成功,也可以回滚,导致你读到的数据不一定是对的。
- 优点:效率最高,并发最强
- 缺点:可能导致脏读、幻读、非重复读,安全性最低
2)读提交(Read Committed,RC,Oracle 默认的隔离级别)
- 定义:事务在提交之前,做的变更都无法被其它事务看见,但是事务本身可以看到数据库的变更。
- 说人话:一个事务里可以读到别人已经提交的变更,即读取最新一份快照数据。如果一个事务里有两次读,两次读的期间别人完成了操作,就会导致两次读的虽然都是真的(不是脏读),但是结果不一样(非重复读)。
- 优点:效率稍高,并发稍强
- 缺点:可能导致幻读、非重复读,安全性较低
3)可重复读(Repeatable Read,RR,MySQL 默认的隔离级别)
- 定义:事务总是只能看到在启动的那个时刻,数据库的状态,即在事务未提交之前,自己做的变更别的事务看不见,数据库中的变更自己也看不见。
- 说人话:一个事务里,不会读取别的事务提交的变更,而是会读取事务开始时的快照数据。
- 优点:不会导致脏读、非重复读
- 缺点:当快照读和当前读混用时,会导致幻读
MySQL Innodb 引擎是如何避免幻读的
MySQL Innodb 引擎做了特殊实现,即加锁。在 RR 级别下,如果查询条件使用了唯一索引,就会对读取到的记录加上行锁;如果查询条件是一个范围查询,那么就会对读取到的记录加上 gap 锁(间隙锁)或者 next-key 锁 ( 行锁 + 间隙锁),避免幻读。
一个事务里,不会读取别的事务提交的变更,而是会读取事务开始时的快照数据。
很多人无法区分幻读与不可重复读,这里简单做个对比。
幻读与不可重复读的区别
不可重复读:在一个事务中多次读取同一个数据时,结果出现不一致。
幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。
4)串行化(Serializable)
- 定义:事务对某一行的操作会加锁,“写”会加“写锁”,“读”会加“读锁”,在锁释放掉之前,其它的事务都无法都这一行的记录进行操作。必须等之前的事务执行完毕,释放锁。后面的事务又会重新加锁。
- 说人话:事务执行的时候,会将使用的数据范围锁起来不让别人用,所有事务排队执行。
- 优点:安全性最高
- 缺点:效率最低、并发最低
总结一下:
隔离级别 | 是否允许脏读 | 是否允许非重复读 | 是否允许幻读 |
---|---|---|---|
读未提交(RU) | √ | √ | √ |
读提交(RC) | X | √ | √ |
可重复读(RR) | X | X | √(MySQL InnoDB 是 X) |
串行化 | X | X | X |
隔离的越严实,效率就越低,并发越低,安全性越高。
5、事务的实现原理
事务的隔离性是通过锁实现的,事务的一致性和持久性是通过 Redo Log (重做日志)实现的,事务的原子性是通过 Undo Log (回滚日志)实现的。
5.1、隔离性的实现
事务的实现是基于数据库的存储引擎。不同的存储引擎对事务的支持程度不一样。MySQL 中支持事务的存储引擎有 InnoDB 和 NDB。
InnoDB 是 MySQL 默认的存储引擎,默认的隔离级别是 RR(可重复读),并且在 RR 的隔离级别下更进一步:
- 用多版本并发控制(MVCC )解决不可重复读问题:MVCC 允许一个行记录可能有不止一个快照数据,因此使用快照读就可以实现可重复读。
- 用行锁 + 间隙锁解决幻读问题:行锁避免了数据的 update/delete,间隙锁避免了数据的 insert 。
因此 InnoDB 的 RR 隔离级别其实也实现了串行化级别的效果,而且保留了比较好的并发性能。
5.2、一致性和持久性的实现
在 InnoDB 的存储引擎中,事务通过 Redo Log 和 InnoDB 存储引擎的日志缓冲( InnoDB Log Buffer ) 实现一致性和持久性。
- 事务开启时:事务中的操作,都会先写入存储引擎的日志缓冲中,在事务提交之前,这些缓冲的日志都需要提前刷新到磁盘上持久化,这就是 DBA 们口中常说的“日志先行”(Write-Ahead Logging)。
- 事务提交之后:在 Buffer Pool 中映射的数据文件才会慢慢刷新到磁盘。此时如果数据库崩溃或者宕机,那么当系统重启进行恢复时,就可以根据 Redo Log 中记录的日志,把数据库恢复到崩溃前的一个状态。未完成的事务,可以继续提交,也可以选择回滚,这基于恢复的策略而定。
5.3、原子性的实现
在事务执行的过程中,除了记录 Redo Log,还会记录 Undo Log 。
Undo Log 主要为事务的回滚服务。Undo Log 记录了数据在每个操作前的状态,如果事务执行过程中需要回滚,就可以根据 Undo Log 进行回滚操作。
单个事务的回滚,只会回滚当前事务做的操作,并不会影响到其他的事务做的操作。
6、MySQL 事务控制
MySQL 里的事务控制语句有:
- 开启事务:
begin
或start transaction
tables 被执行。 - 提交事务:
commit
或commit work
,使对数据库进行的所有修改成为永久性的。 - 回滚事务:
rollback
或rollback work
,会结束用户的事务,并撤销正在进行的所有未提交的修改。注意,所有的 DDL 语句是不能回滚的,并且部分的 DDL 语句会造成隐式的提交。 - 创建保存点:
savepoint identifier
,一个事务中可以有多个 savepoint; - 删除保存点:
release savepoint identifier
,当没有指定的保存点时,执行该语句会抛出一个异常 - 回滚事务到保存点:
rollback to identifier
- 设置事务的隔离级别:
set transaction
- 修改自动提交模式:
set autocommit=0
代表禁止自动提交,=1
代表开启自动提交。