事务(transaction)是在数据库上执行的一个或多个操作构成的序列,用来完成数据库系统的高级功能。
事务启动(start):BEGIN;
事务提交(commit):COMMIT;
将事务对数据库的修改持久地写到数据库中
事务中止(abort):ROLLBACK;
将事务对数据库的修改全部撤销(undo),就像事务从未执行过
事务可以中止自己,也可以被DBMS中止
事务的操作要么全部执行,要么一个也不执行
事务的执行只有两种结局
中止事务(abortedtxn)执行过的操作必须撤销(undo)
如果事务的程序正确,并且事务启动时数据库处于一致状态(consistent state),则事务结束时数据库仍处于一致状态
一个事务的执行不受其他事务的干扰
多个事务的执行有2种方式
串行执行(serial execution) ⇒ 不破坏隔离性
交叉执行(interleaving execution) ⇒ 可能破坏隔离性
DBMS保证事务的隔离性
事务一旦提交,它对数据库的修改一定全部持久地写到数据库中
故障(failure)导致事务对数据库的修改有4种结果
调度(schedule)是一个或多个事务的重要操作的序列。
如果一个调度中不同事务的操作没有交叉(interleave),则该调度是串行调度(serial schedule)。
不是串行调度的调度称为非串行调度(nonserial schedule)。
每个事务孤立执行(executedinisolation),都会将数据库从一种一致性状态(consistent state)变为另一种一致性状态。
任意串行调度都能保持数据库的一致性
非串行调度可能会破坏数据库的一致性
非串行调度会导致事务的异常行为(anomaly behavior),从而破坏数据库的一致性
T1提交之前,T1写入的值已被T2覆盖。
如果两个调度在任意数据库实例上的效果都相同,则等价(equivalent)。
如果一个调度等价于某串行调度,则该调度是可串行化调度(serializableschedule)
可串行化调度的并发度低
在某些场景下,并发事务不需要严格隔离
一个事务对部分对象的修改可以暴露(expose)给对其他并发事务
弱隔离级别(weakerisolationlevel)可以提高系统的并发度
在不同隔离级别下,一个事务修改过的对象的值对其他并发事务的可见程度不同
SETTRANSACTIONISOLATIONLEVEL;
未提交事务(uncommittedtxn)所做的修改对其他事务可见
只有已提交的事务(committedtxn)所做的修改才对其他事务可见。
如果一个事务不修改对象X的值,则该事务在任何时候读到的X值都等于事务启动时读到的X值。
支持可串行化隔离级别的DBMS实施(enforce)的都是冲突可串行化
冲突可串行化比一般可串行化的条件更严
冲突可串行化更便于在DBMS中实施
两个操作冲突(conflict),如果
写-写冲突可能导致脏写(dirtywrite)
写-读冲突可能导致脏读(dirty read)
读-写冲突可能导致不可重复读(unrepeatable read)
两个调度冲突等价(conflictequivalent),如果这两个调度涉及相同事务的相同操作
如果一个调度冲突等价于某个串行调度,则该调度是冲突可串行化调度。
既然事务要满足事务的隔离性的原因是提高并行度
非冲突可串行化调度
第1步: 将调度S表示为优先图(precedence graph)
每个顶点代表S中的一个事务
从事务Ti到事务Tj有一条有向边(arc),如果Ti的某个操作oi和Tj的某个操作oj冲突,并且oi在S中先于oj
第2步: S是冲突可串行化调度,当且仅当其优先图没有环(acyclic)
视图可串行化是比冲突可串行化更弱的概念
两个调度S1和S2视图等价,如果
如果一个调度视图等价于某个串行调度,则该调度是视图可串行化调度(view serializable schedule)。
冲突可串行化调度一定是视图可串行化调度
视图可串行化调度不一定是冲突可串行化调度
并发控制协议用于对并发事务实施正确的(运行时)调度,而无需预先确定整个(静态)调度
用锁(lock)来保护数据库对象
事务Ti只有获得了对象A的锁,才能操作A
如果事务Ti请求了对象A的锁,但并未获得,则Ti开始等待,直至获得A的锁为止
如果事务Ti已经获得了对象A的锁,则在Ti完成对A的操作后,Ti必须释放A的锁
LOCK(A): 请求对A加锁。
UNLOCK(A): 释放A的锁。
共享锁(shared lock)/S锁(S-lock)
互斥锁(exclusive lock)/X锁(X-lock)
如果对象A上有事务Ti加的共享锁,则事务Tj还可以对A加共享锁,但不可以对A加互斥锁
如果对象A上有事务Ti加的互斥锁,则事务Tj对A既不能加共享锁,也不能加互斥锁
S-LOCK(A): 请求对A加共享锁
X-LOCK(A): 请求对A加互斥锁
每个事务的执行分为两个阶段
增长阶段(Growing Phase)
萎缩阶段(Shrinking Phase)
级联中止(CascadingAborts):一个事务中止可能会导致其他事务级联中止(cascading abort)。
严格调度不会引发级联中止。
增长阶段(GrowingPhase)
萎缩阶段(ShrinkingPhase)
严格2PL保证生成严格的冲突可串行化调度,因而不会产生级联中止
一组事务形成死锁(deadlock),如果每个事务都在等待其他事务释放锁。
措施1:死锁检测(DeadlockDetection)
措施2:死锁预防(DeadlockPrevention)
等待图(waits-forgraph)
事务产生死锁当且仅当等待图中有环(cycle)
从等待图的环(cycle)中选择一个事务作为“牺牲品”,中止该事务。
选择“牺牲品”时需要考虑多种因素
还要考虑事务“被牺牲”的次数,防止事务“饿死”(starvation)
当事务启动时,DBMS为事务分配一个唯一且固定的优先级(priority)
当事务Ti请求事务Tj拥有的锁而无法获得时,DBMS根据Ti和Tj的优先级来裁决如何处理T1和T2。
规则1: Wait-Die(“Old Waits for Young”)
规则2: Wound-Wait(“Young Waits for Old”)
两个规则不能混合使用
锁越多,管理锁的开销越大
一个事务访问1亿条元组,就需要加1亿把锁
在不合适的粒度上加锁会降低事务并发度
一个事务只需访问1条元组,却在整个关系上加锁
提高锁管理的效率尽可能少加锁
在合适的粒度上加锁
任何事务都要服从下列规则:
从最高级别对象开始加锁,加锁过程自顶向下
对一个对象加IS或S锁之前,必须先获得其父亲对象的IS锁
事务对一个对象加IX、SIX或X锁之前,必须先获得其父亲对象的IX锁
解锁过程自底向上
如果一个事务已经请求了大量低级别对象上的锁,则DBMS动态地将这些锁升级为上一级别对象上的锁
前面假设事务只执行读操作(read)和更新操作(update)
冲突可串行化调度只在固定的数据库上保证
如果事务还执行插入(insert)或删除(delete)操作,则可能出现幻读问题(phantomproblem)
原因: T1只能锁定t中id>1的元组,无法给不存在的元组加锁
可以用谓词锁(predicatelock)来解决幻读问题。谓词锁的实现代价很高
(教妹学数据库系统)(十一)并发控制 - it610.com