数据库事务简介

2024-09-21 database

简介

当前很多数据库的 ACID 特性会基于 MVCC Snapshot Isolation 来实现,这样读写不会相互阻塞,同时能够提供原子能力。另外,关于 MySQL 的可以参考 InnoDB 隔离级别 中的介绍。

ACID

Atomic 原子性、Consistency 一致性、Isolation 隔离性、Durability 持久化的简写,如下简单整理相关基本概念。

Atomic: 要么全部生效 (Commit),要么全部撤销 (Rollback),不会出现中间态
  > 日志管理
    Redo 相关修改操作需要在提交前落盘
    Undo 用于故障恢复以及事务回退

Consistency: 并发执行过程中,确保数据的合法性,满足完整性约束
  > 业务层面保证,转账不会出现已经扣减但是未收到情况
  > 各种约束,例如唯一性、外键、触发器等

Isolation: 确保并发事务是可串行化的,可见性和性能的均衡
  > 并发控制,消除可能带来的异常,脏读、不可重复读、幻读等
    通常会采用两阶段锁、MVCC/Snapshot、时间戳、乐观并发控制
  > 隔离级别
    未提交读 (Read Uncommitted)
    已提交读 (Read Committed)
    可重复读 (Repeatable Read)
    可串行化 (Serializability)
    Snapshot (Snapshot Isolation)

Durability: 保证已经提交数据不会丢失
  > 缓冲区脏页刷新,可以是异步,同时考虑污染
  > Checkpoint 机制,防止日志无限膨胀
  > Redo/Undo 用于故障恢复
  > 复制,可以是逻辑复制、物理复制,不同场景要求不同

MVCC

自从发明了 Multi-Version Concurrency Control, MVCC 之后,几乎所有的数据库都开始抛弃 (部分抛弃) 性能较差的两阶段锁的并发控制,所以,后续兴起分布式系统几乎都采用 MVCC 的并发控制方案。

异常

其中左侧的是 A 事务,而右侧是 B 事务。

Dirty Write

未提交的事务 A 不受并发 B 事务写的影响。

a> BEGIN;
a> INSERT INTO genres VALUES (5, 'Western');
                                                   b> INSERT INTO genres VALUES (5, 'Romance');
                                                   Error: Serialization failure, retry transaction
a> SELECT * FROM genres WHERE id = 5;
5|Western

当写冲突时会存在一个事务报错,可以根据回退成本选择,或者简单点,回退后面的事务。

Dirty Read

未提交的事务 A 在事务 B 中不可见,提交之后对事务 B 才可见。

a> BEGIN;
a> INSERT INTO genres VALUES (5, 'Western');
                                                  b> SELECT * FROM genres WHERE id = 5;
                                                  No rows returned
a> COMMIT;
                                                  b> SELECT * FROM genres WHERE id = 5;
                                                  5|Western

Lost Update

两个事务 A B 同时读取一个值,当依次更新时,首先写入的不应该被后面的写入覆盖。

a> BEGIN;                                         b> BEGIN;
a> SELECT title, rating FROM movies WHERE id = 2; b> SELECT title, rating FROM movies WHERE id = 2;
Sicario|7.6                                       Sicario|7.6
a> UPDATE movies SET rating = 7.8 WHERE id = 2;
                                                  b> UPDATE movies SET rating = 7.7 WHERE id = 2;
                                                  Error: Serialization failure, retry transaction
a> COMMIT;

此时合理的处理逻辑是,重启一个事务进行读取+更新操作。

Fuzzy Read

事务 B 不应该立即看到某个事务的修改,即使该事务已经提交。

a> BEGIN;                                         b> BEGIN;
                                                  b> SELECT * FROM genres WHERE id = 1;
                                                  1|Science Fiction
a> UPDATE genres SET name = 'Scifi' WHERE id = 1;
a> COMMIT;
                                                  b> SELECT * FROM genres WHERE id = 1;
                                                  1|Science Fiction
                                                  b> COMMIT;

                                                  b> SELECT * FROM genres WHERE id = 1;
                                                  1|Scifi

Read Skew

当事务 A 读取两个值时,如果 B 在读取中间修改了某个值,那么 A 读取到的应该是老的值。

a> BEGIN;
a> SELECT * FROM genres WHERE id = 2;
2|Action
                                                  b> BEGIN;
                                                  b> UPDATE genres SET name = 'Drama' WHERE id = 2;
                                                  b> UPDATE genres SET name = 'Action' WHERE id = 3;
                                                  b> COMMIT;
a> SELECT * FROM genres WHERE id = 3;
3|Drama

Phantom Read

当事务 A 查询某个范围时,而此时 B 提交满足 A 查询条件的数据,那么 A 不应该查询到 B 写入的数据。

a> BEGIN;
a> SELECT * FROM genres WHERE id > 2;
3|Drama
4|Comedy
                                                  b> INSERT INTO genres VALUES (5, 'Western');
a> SELECT * FROM genres WHERE id > 2;
3|Drama
4|Comedy

Write Skew

Write skew: when A reads row X and writes it to row Y, B should not concurrently be able to read row Y and write it to row X.

a> BEGIN;                                         b> BEGIN;
a> SELECT * FROM genres WHERE id = 2;
2|Action
                                                  b> SELECT * FROM genres WHERE id = 3;
                                                  3|Drama
                                                  b> UPDATE genres SET name = 'Drama' WHERE id = 2;
a> UPDATE genres SET name = 'Action' WHERE id = 3;
a> COMMIT;                                        b> COMMIT;

Here, the writes actually go through. This anomaly is not protected against by snapshot isolation, and thus not by toyDB either - doing so would require implementing serializable snapshot isolation. However, this is the only common serialization anomaly not handled by toyDB, and is not among the most severe.

参考