三问讲清事务原子性
· · ·本文将从ACID四个特性结合三大日志和MVCC讲解MySql事务的实现
众所周知ACID是Innodb引擎数据库事务的四个基本特性,用来保证数据库操作的可靠性与一致性:
A(Atomicity,原子性):事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败回滚,不会存在成功或者失败的中间态。
C(Consistency,一致性):事务将数据库从一个一致的状态转变到另一个一致的状态,不会破坏数据的完整性约束。
I(Isolation,隔离性):多个事务并发执行时,一个事务的执行不应影响其他事务的执行,如同它们是串行执行一样。
D(Durability,持久性):一旦事务提交,其所做的修改就会永久保存在数据库中,即使系统崩溃也不会丢失。
当业务逻辑没有问题的情况下,满足原子性、隔离性、持久性,一致性会自动得到保证。
借用经典转账例子,有一张用户表accounts
user_id | username | balance
1 | a | 10
2 | b | 0
A账户给B转账10元,简单来说只需要执行两步,A账户减10元,B账户加10元
-- 开始事务A
START TRANSACTION;
-- 执行转账 SQL 语句
UPDATE accounts SET balance = balance - 10 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 10 WHERE user_id = 2;
-- 提交事务
COMMIT;
何为原子性?
一句话概括就是执行的SQL语句要么全成功,要么全失败,不会存在其他态(私密马赛一不小心单押上了),此为事务的原子性。
假如事务执行到一半失败(比如事务中的某条SQL报错、服务挂了、服务器挂了)或者用户主动发起ROLLBACK会滚,那数据库将会执行回滚,回滚到修改之前的版本。 那回滚是通过什么实现的呢?
什么是Undo log?
Undo Log(回滚日志)是InnoDB存储引擎提供的一种逻辑日志。
一句话概括就是Undo Log通过链表记录事务修改数据的不同版本,也就是修改前+修改后的数据。主要有两大作用:
保障事务原子性,当事务执行失败或者主动发起回滚时,将数据回滚到修改之前的版本
为MVCC(多版并发控制)在SELECT时提供数据的历史版本来间接支撑数据的隔离性(这里放到隔离性再讲)
Undo log如何保障事务的原子性?
一句话概括Undo log通过链表来记录数据不同版本,如果事务执行失败,通过链表找到修改之前的数据版本(旧值),然后将修改后的数据(新值)修改为旧值。
当一个事务开始时,InnoDB会为该事务分配一个唯一的事务ID(trx_id),但此时尚未生成Undo Log。
当一个事务执行到增删改操作时,InnoDB并不会直接修改磁盘上的数据页,而是先记录旧数据,再修改内存中的数据页。
update:记录被修改列的旧值(比如balance从10变为0, Undo Log里存的就是10)
DELETE:记录整行完整旧数据,相当于插入一条“旧版本行”(用户回滚执行INSERT完整行插回原位置)
INSERT:记录改行数据的主键信息,如果没有表中没有主键,但是有一个或多个唯一非空索引(UNIQUE NOT NULL),InnoDB会选择第一个索引,如果两者都不存在则会在表内部为每一行数据隐式生成一个用于标识的唯一“Row ID”(用户回滚时执行DELETE)
如果发生回滚时,根据Undo log的记录获取到表ID(table_id),根据table_id,InnoDB查询内存中的数据字典(元数据缓存)获取到表完整结构,借助元数据解析“还原”Undo Log数据,并根据修改类型,执行对应恢复操作,至此该条记录回滚完成。事务完成后,事务所占用的锁资源会被释放,但是该事务产生的Undo Log并不会被立即删除,而是确定没有其他事务需要(实现MVCC读取历史版本)时再异步清理。
那如果全部都执行成功了呢?那就要进行到下一步持久化
当前行 (user_id=1) 的隐藏字段:
trx_id: 200
roll_ptr: --> [undo log2]
undo log2 (trx_id=200):
| trx_id: 200 | roll_ptr: --> | old_balance: 0 | ... |
|
v
undo log1 (trx_id=100):
| trx_id: 100 | roll_ptr: null | old_balance: 10 | ... |
那么正常流程下SQL执行成功喜大普奔,数据通过mysql写入硬盘,等待下一次增删改查。也就是事务的持久性
Durability 持久性
mysql需要redolog来实现