Innodb 与 ACID 模型
事务
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务就是一组原子性的操作,这些操作要么全部发生,要么全部不发生。事务把数据库从一种一致性状态转换成另一种一致性状态。
ACID理论
ACID理论是指在数据库管理系统(DBMS)中事务所具有的四个特性:
-
原子性(Atomicity)
事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
-
一致性(Consistency)
事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
-
隔离性(Isolation)
一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
-
持续性(Durability)
也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
原子性(Atomicity)
ACID模型的原子性方面主要涉及 InnoDB 事务,相关的 MySQL 特性包括:
-
autocommit 配置;
开启自动提交的方法,在配置文件中指定如下配置即可,默认是开启的:
[mysqld] autocommit=1
-
COMMIT 语句;
-
ROLLBACK 语句。
一致性(Consistency)
ACID模型的一致性主要涉及 InnoDB 内部处理以保护数据免于崩溃。相关 MySQL 特性包括:
-
Innodb 的双写缓冲区(doublewrite buffer)
-
InnoDB 崩溃恢复
隔离性(Isolation)
ACID模型的隔离方面主要涉及 InnoDB 事务,特别是应用于每个事务的隔离级别。相关 MySQL 特性包括:
-
autocommit 配置;
-
事务隔离级别和 SET TRANSACTION 语句;
-
InnoDB 锁定底层的细节。
持久性(Durability)
ACID 模型的持久性方面涉及与特定硬件配置交互的 MySQL 软件功能。由于根据 CPU、网络和存储设备的功能存在多种可能性,因此,在这方面提供具体指导是最复杂的,它可以为我们采购硬件提供指导方针。相关的 MySQL 功能包括:
-
Innodb 的双写缓冲区(doublewrite buffer);
-
innodb_flush_log_at_trx_commit
配置; -
sync_binlog
配置; -
innodb_file_per_table
配置; -
存储设备中的写入缓冲区;
存储设备例如,磁盘驱动器、SSD 或 RAID 阵列;
-
存储设备中由电池供电的缓存;
-
用于运行 MySQL 的操作系统,特别是它对
fsync()
系统调用的支持; -
不间断电源 (UPS) 保护运行 MySQL 服务器和存储 MySQL 数据的所有计算机服务器和存储设备的电源;
-
备份策略,例如备份频率和类型以及备份保留期;
-
对于分布式或托管数据应用程序,MySQL 服务器硬件所在的数据中心的特定特征以及数据中心之间的网络连接。
InnoDB的事务实现原理
InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?
-
持久性:通过 redo log(重做日志)来保证
每一个事务提交后,会先将该事务的所有SQL日志写入到重做日志文件进行持久化,再写入缓存,最后更新数据库,InnoDB存储引擎在启动时,不管上次数据库运行时是否正常关闭,都会尝试通过redo log进行恢复操作。
-
原子性:通过 undo log(回滚日志)来保证
在事务里任何对数据的修改都会写一个undo log,然后进行数据的修改,如果出现错误,存储引擎会利用undo log的备份数据恢复到事务开始之前的状态。
-
隔离性:通过 MVCC(多版本并发控制)和锁机制来保证
写-写操作通过锁来实现,写-读操作通过 MVCC 实现的。
-
一致性:通过持久性 + 原子性 + 隔离性来保证
事务的原子性、隔离性、持久性保证了数据的一致性。
脏读、幻读、不可重复读
在介绍事务隔离级别前,先了解几个概念:脏读、幻读、不可重复读
-
脏读:事务 A 读取了事务 B 更新的数据,然后 B 回滚操作,那么 A 读取到的数据是脏数据。
-
不可重复读:事务 A 多次读取同一数据,事务 B 在事务 A 多次读取的过程中,对数据作了更新并提交,导致事务 A 多次读取同一数据时,结果 不一致。
-
幻读:系统管理员 A 将数据库中所有学生的成绩从具体分数改为 ABCDE 等级,但是系统管理员 B 就在这个时候插入了一条具体分数的记录,当系统管理员 A 改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
不可重复读侧重于修改,幻读侧重于新增或删除(多了或少量行),脏读是一个事务回滚影响另外一个事务。
MySQL的事务隔离级别
以下是 SQL-92 定义的四种事务隔离级别标准:
-
Read Uncommitted(读取未提交内容)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
-
Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓 的 不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的 commit,所以同一 select 可能返回不同结果。
-
Repeatable Read(可重读)
这是 MySQL 的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。
-
Serializable(可串行化)
通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
在不同的隔离级别下,并发事务可能发生的现象也不同:
MySQL的事务隔离级别如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | \(\checkmark\) | \(\checkmark\) | \(\checkmark\) |
READ-COMMITTED | \(\times\) | \(\checkmark\) | \(\checkmark\) |
REPEATABLE-READ | \(\times\) | \(\times\) | \(\checkmark\) |
SERIALIZABLE | \(\times\) | \(\times\) | \(\times\) |
注:\(×\) 即不会出现相应的问题,\(√\) 即会出现该类问题。
需要注意,理论上,在 RR 隔离级别下,会出现幻读现象。但是,如果将存储引擎选为 InnoDB ,在 RR 级别下幻读的问题就会被解决,InnoDB 通过 Next-Key Lock ,即能锁定该记录,又能阻止其他事务将新纪录插入到被保护记录前面的间隙中,从而解决了幻读问题。
行级锁的类型主要有三类:
- Record Lock:记录锁,也就是仅仅把一条记录锁上;
- Gap Lock:间隙锁,锁定一个范围,但是不包含记录本身;
- Next-Key Lock:Record Lock + Gap Lock 的组合,锁定一个范围,并且锁定记录本身。
例如,当事务 A 更新了所有记录的某个字段,此时事务A会获得对这个表的表锁,在事务A提交前,事务 A 获得的锁不会释放。此时,事务 B 在该表插入新记录,会因为无法获得该表的锁,而导致插入操作被阻塞,只有事务A提交了事务后,释放了锁,事务B才能进行接下去的操作。
事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
参考: