事务¶
事务概念¶
事务的四大特性(ACID):
- 原子性:所有操作要么全部执行,要么全部不执行。
- 一致性:事务执行之前和执行之后数据库的完整性没有被破坏。
- 隔离性:多个事务之间不会相互影响。
- 持久性:事务一旦提交,对数据库的修改就是永久性的,即使系统崩溃。
事务从活动状态开始,当完成最后一条语句后,事务就进入了部分提交状态。此时,事务已经完成,但对数据库的修改还没有提交,仍有可能失败。在成功写入修改后,就进入提交状态。
如果事务执行失败,为了保证原子性,则需要执行事务的回滚,恢复事务执行前的状态,即事务中止。通常的做法是维护一个日志,记录事务对数据库的修改。
事务的隔离级别:
- 可串行化:事务串行执行;
- 可重复读:只允许读取已提交的数据,而且在一个事务两次读取一个数据项期间,其他事务不得更新该数据;
- 读已提交:只允许读取已提交的数据,但不要求可重复读。比如,在一个事务两次读取一个数据项期间,另一个事务更新了该数据并提交;
- 读未提交:允许读取未提交的数据。
以上级别都不允许脏写,即如果一个数据项已被另一个尚未提交或中止的事务写入,则不允许对该数据项执行写操作。
并发控制¶
锁¶
- 共享锁 S(Shared Lock):如果一个事务获得了一个数据项的共享锁,则该事务可以读但不可以写,其他事务可以读取该数据项,也不能修改;
- 排他锁 X(Exclusive Lock):如果一个事务获得了一个数据项的排他锁,则该事务既可读也可写,其他事务不能读取该数据项,也不能修改。
共享锁和共享锁是相容的,而排他锁和排他锁是互斥的。在任何时候,一个数据项上可同时有被不同事务持有的多个共享锁,而排他锁的请求必须等待所有共享锁的释放。
在事务 T 获得了某个数据项的共享锁后,另一个事务 T1 试图获得该数据项的排他锁,T1 必须等待 T 释放共享锁,但如果不停有其他事务申请对该数据项的共享锁,那么 T1 可能一直获取不到排他锁,这称为饿死。避免饿死的方式时,只有满足以下条件,才能通过事务 T 对数据项的 M 型锁申请:
- 在该数据项上不存在持有与 M 型锁冲突的锁的其他事务;
- 在该数据项上不存在等待加锁且先于事务 T 申请加锁的事务。
多粒度锁¶
当事务对树上一个结点显式地加上锁时,该事物也给该结点的全部后代隐式地加上了同类型的锁。
问题在于,当申请给某个结点加锁时,需要先判断其子结点是否持有冲突的锁。为了更快地判断锁冲突,在对一个结点显式地加锁前,需要对该结点的祖先结点加上意向锁(Intention Lock)。
IS | IX | S | SIX | X | |
---|---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 | 不兼容 |
SIX | 兼容 | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
MySQL 中的加锁级别分为表锁、页锁和行锁。其中,意向锁和行锁不会互斥,只可能和表级锁互斥。
删除和插入¶
在删除一个数据项前,先在该数据项上请求排他锁。
在插入一个数据项时,在新创建的数据项上加排他锁。
参考¶
- 数据库系统概念. 原书第 6 版. 第四部分.