数据库 ACID 四大特性到底为了啥,一文带你看通透,java 支付宝支付接口教程
这里引用银行转账的例子,假设银行的数据库有两张表:信用卡(credit)表和储蓄(savings)表。用户陈哈哈要把信用卡里最后 100 块钱额度转到他 的储蓄账户用来吃饭,那么需要至少三个步骤:
检査信用卡余额是否髙于 100 块钱。
从信用卡账户余额中减去 100 块钱。
在储蓄账户余额中增加 100 块钱。
上述三个步骤必须在同一个事务中执行,任何一个 SQL 失败,则必须回滚所有的 SQL。这里用START TRANSACTION
语句开启事务,要么使用COMMIT
提交事务将修改的数据持久保留,要么使用ROLLBACK
销所有的修改。事务 SQL 的样本如下:
START TRANSACTION;
-- 检查信用卡账户额度
SELECT balance FROM credit WHERE customer_id = 'chenhh';
-- 信用卡表扣钱
UPDATE credit SET balance = balance - 100.00 WHERE customer_id = 'chenhh';
-- 储蓄表加钱
UPDATE savings SET balance = balance + 100.00 WHERE customer_id = 'chenhh';
COMMIT;
试想一下,如果执行到第四条语句时服务器崩溃了,会发生什么?废话,我被坑了 100 块钱,中午只能饿肚子!再假如,在执行到第三条语句和第四 条语句之间时,同一时间,另外一个进程,来自商场结账的女朋友,也要信用卡账户的 100 块,那么结果可能就是银行在不知道这个逻辑的情况下白白给了陈哈哈女朋友 100 块钱?
别做梦了,主流关系型数据库(如 MySQL、Oracle 等)是通过了严格的 ACID 测试,不会随便出这种 BUG 的。那么我们今天就一起来搞懂 ACID 的核心原理。
[](
)一、ACID 特性
=======================================================================
原子性(Atomicity)
单个事务,为一个不可分割的最小工作单元
,整个事务中的所有操作要么全部 commit 成功,要么全部失败 rollback,对于一个事务来
说,不可能只执行其中的一部分 SQL 操作,这就是事务的原子性。
一致性(Consistency)
数据库总是从一个一致性的状态转换到另外一个一致性的状态。在前面的例子中, 一致性确保了,即使在执行第三、四条语句之间时系统崩潰,信用卡账户也不会损 失 100 块,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中,保证数据一致性。
隔离性(Isolation)
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面 的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个账户查询余额 SQL 开始运行,则其看到的信用卡账户的余额并没有被减去 100 元。后面我们讨论隔离级别(Isolation level)的时候,会发现为什么我们要说事务通常来说是不可见的
。
持久性(Durability)
一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。
事务的 ACID 特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难, 甚至可以说是不可能完成的任务。一个兼容 ACID 的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,才能确保 ACID 的实现。
[](
)二、ACID 具体实现
=========================================================================
对 MySQL 来说,逻辑备份日志(binlog)、重做日志(redolog)、回滚日志(undolog)、锁技术 + MVCC就是MySQL实现事务的基础
。
原子性:通过 undolog 来实现。
持久性:通过 binlog、redolog 来实现。
隔离性:通过(读写锁+MVCC)来实现。
一致性:
MySQL通过原子性,持久性,隔离性最终实现(或者说定义)数据一致性。
学习 MySQL 日志可参考[《听我讲完 redo log、binlog 原理,面试官老脸一红》](
)
学习 MySQL 锁机制可参考 [《面试让 HR 都能听懂的 MySQL 锁机制,欢声笑语中搞懂 MySQL 锁》](
)
[](
)1、原子性原理
事务通常是以 BEGIN TRANSACTION 开始,以 COMMIT 或 ROLLBACK 结束。
COMMIT 表示提交
,即提交事务的所有操作并持久化到数据库中。ROLLBACK表示回滚
,即在事务中运行的过程中发生了某种故障,事务不能继续执行,系统将事务中对数据库所有已完成的操作全部撤销,回滚到事务开始时的状态,这里的操作指对数据库的更新操作,已执行的查询操作不用管。这时候也就需要用到 undolog 来进行回滚。
undolog:
每条数据变更(INSERT/UPDATE/DELETE/REPLACE)等操作都会生成一条 undolog 记录,在 SQL 执行前先于数据持久化到磁盘。
当事务需要回滚时,MySQL 会根据回滚日志对事务中已执行的 SQL 做逆向操作,比如 DELETE 掉一行数据的逆向操作就是再把这行数据 INSERT 回去,其他操作同理。
[](
)2、持久性原理
先了解一下 MySQL 的数据存储机制,MySQL 的表数据是存放在磁盘上的,因此想要存取的时候都要经历磁盘 IO,然而即使是使用 SSD 磁盘 IO 也是非常消耗性能的。为此,为了提升性能 InnoDB 提供了缓冲池(Buffer Pool),Buffer Pool 中包含了磁盘数据页的映射,可以当做缓存来使用:
读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;
写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;
我们知道,MySQL 表数据是持久化到磁盘中的,但如果所有操作都去操作磁盘,等并发上来了,那处理速度谁都吃不消,因此引入了缓冲池(Buffer Pool)
的概念,Buffer Pool 中包含了磁盘中部分数据页的映射,可以当做缓存来用;这样当修改表数据时,我们把操作记录先写到 Buffer Pool 中,并标记事务已完成,等 MySQL 空闲时,再把更新操作持久化到磁盘里(你可能会问,到底什么时候执行持久化呢?1、MySQL线程低于高水位;2、当有其他查询、更新语句操作该数据页时
),从而大大缓解了 MySQL 并发压力。
但是它也带来了新的问题,当 MySQL 系统宕机,断电时 Buffer Pool 数据不就丢了?
因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们急需一种机制需要存一下已提交事务的数据,为恢复数据使用。
于是 redo log + binlog 的经典组合就登场了,这里不在扩展赘述。可参考[《听我讲完 redo log、binlog 原理,面试官老脸一红》](
)
[](
)3、隔离性原理
隔离性是事务 ACID 特性里最复杂的一个。在 SQL 标准里定义了四种隔离级别,每一种级别都规定一个事务中的修改,哪些是事务之间可见的,哪些是不可见的。
级别越低的隔离级别可以执行越高的并发,但同时实现复杂度以及开销也越大。
搞懂 MySQL 事务隔离级别请参考[《上个厕所的功夫,搞懂 MySQL 事务隔离级别》](
)
Mysql 隔离级别有以下四种(级别由低到高):
评论