事务 ~ 锁
事务
Transaction,数据库操作序列组成的单个逻辑执行单元,要么全部执行,要么全不执行(同生共死)。事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。事务需手动启动和结束,事务是 恢复 和 并发控制 的基本单位。
ζ 声明创建
begin transaction 事务名|(事务名称)变量 with mark SQL处理语句
其中,begin使@@TRANCOUNT加1,commit/rollback使@@TRANCOUNT减1,但是回滚到savapoint不影响其值。通过@@TRANCOUNT可以查看当前活动中的事务数量,一个事务完整执行完毕,@@TRANCOUNT应为0;
ζ 保存点
save transaction 保存点名|(保存点名称)变量
ζ 嵌套
只有显式事务可以嵌套。
ζ 提交
· 显式提交:commit transaction 事务名|(事务名称)变量;
· 自动提交:执行 DCL或DDL 语句(导致事务立即提交),或程序正常退出;
ζ 回滚
· 显式回滚:rollback transaction 事务名|(事务名称)变量|保存点名|(保存点名称)变量;
· 自动回滚:系统错误或强行退出;
注:提交和回滚都会结束当前事务,commit之后不能再rollback,但可以采用闪回flashback恢复数据。检查点机制周期地检查事务日志(数据操作和任务操作信息)检查事务的执行状态。
优点
- 维护数据库数据的 正确性、完整性、一致性、可恢复性;
- 简化错误恢复,可靠性高;
特征:ACID属性
- 原子性 Atomicity:事务是应用中不可再分的最小逻辑执行单位体;
- 一致性 Consistency:事务结束后,数据库内的数据是合法正确的;
- 隔离性 Isolation:并发执行的事务之间相互独立、互不干扰;
- 持续性 Durability:持久性,Persistence,事务提交后,数据是永久性的、不可回滚;
其中,C 是终极目标, AID 是方法手段。
注意事项
- 事务宜短,避免使用嵌套、while循环、DDL;
- 避免用户交互操作;
参考
并发
数据库支持数据共享,允许多个用户程序并行访问数据,引起并发操作 (Concurrency),即多用户或多事务对同一数据( 同一时间间隔 )进行操作,关键是如何对系统内的多个活动(进程)进行切换。
优缺点
- 对有限物理资源强制多用户共享,复用提高效率;
- 数据不一致性,破环数据的完整性;
并发 .vs. 并行 (Parallelism):
并行性允许多个程序(同一时刻)在不同的CPU上运行。 从微观角度,并行性是多处理机、多进程在同一时刻同时运行,物理上的同时发生,并发性是单处理机下、多个进程在同一时间间隔内运行,逻辑上的同时发生;从宏观角度,并发是表现为并行的。并行性包含同时性和并发性,并行性是并发性的特例。
参考:并行和并发的区别联系;
并发操作 - 数据不一致 + 并发控制;
锁
并发操作破坏事务的隔离性,导致如下问题:
- 丢失修改:事务A对数据的修改覆盖了事务B对数据的修改,事务B对数据的修改丢失;
- 脏读:事务A正在对数据访问修改还未提交,此时事务B访问该数据;
- 不可重复读:在同一事务A中多次读取数据时,事务B对数据修改,导致多次读取的数据不一致;
- 幻读:事务A读取结果集,事务B对结果集增删改,事务A再次查询结果集时,发现数据新增/丢失/修改;
注:幻读和不可重复读都是读取已提交事务的结果(脏读是读取未提交事务的结果)。不可重复读针对同一个数据项,幻读针对一批数据整体。
参考:上述问题解释及解决方法;
数据库通过 封锁机制 (锁+封锁协议)来解决并发访问造成的数据不一致,同步多个用户同时对同一个数据块的访问的一种机制,保证数据库完整性和一致性。锁之间存在兼容性问题,由数据库引擎的 锁管理器 在内部管理。系统的自动锁定管理机制实现动态优化加锁。行版本控制利用类时间戳列(行版本列)完善锁机制。锁定和行版本控制可以防止用户读取未提交的数据,也可以防止多用户同时更改同一数据。
锁的自动优化:锁升级
Lock Escalations,将许多较细粒度的锁转换成数量更少的较粗粒度的锁的过程,多数情况下将众多行锁升级为一个表锁,削减系统开销,但会增加并发争用资源,甚至引发死锁。数据库引擎可以为同一语句执行行锁定和页锁定。
参考:锁升级的问题研究; 关于DB2锁升级相关问题的探讨;
事务隔离级别
事务并发与隔离级别关联。
· 读未提交数据:Read Uncommitted
可能引发脏读、不可重复读、幻读;最低的事务隔离级别,
· 读已提交数据:Read Committed
可能引发不可重复读、幻读,避免脏读;多数数据库的默认隔离级别;
· 可重复读:Repeatable Read
可能引发幻读,避免脏读、不可重复读;MySql的默认隔离级别;
· 可串行化:Serializable
不会引发上述问题;最高的事务隔离级别,强制事务顺序串行执行,对每行数据加锁(锁表),会引发超时和锁竞争,耗资源多、损失并行度、性能低;
参考:数据库事务隔离级别;
分类
-=- 锁定模式 -=-
· 共享锁(Share Lock,S锁, 读锁)
允许并发事务在封闭式并发控制下读取资源但不能更改数据,保证在事务T释放数据对象obj上的锁之前,其他事务不能修改、但可以读取obj,只能在obj上加读锁、不能加写锁,select;
查询操作通常只请求S锁;
· 排他锁(Exclusive Lock,X锁, 写锁, 独占锁)
防止并发事务对同一资源同时更新,保证在事务T释放数据对象obj上的锁之前,其他事务不能读取和修改(insert、update、delete)obj、更不能在obj上加锁,排他锁不与其他任何锁兼容。;
修改操作通常请求X锁和S锁;
· 更新锁(U锁)
可防止常见的死锁,即在多个事务锁定、读取资源以及随后可能进行的资源更新导致死锁。更新锁每次只能分配给一个事务,若要修改资源,更新锁会变成排他锁,否则变为共享锁 ;
· 意向锁(I锁)
I 锁包括意向共享锁 IS、意向排他锁 IX、意向排他共享锁 SIX(S+IX),用于建立锁的层次结构,对任一结点加锁时,必须先对它的上层结点加意向锁,以保护锁层次结构中的底层资源(上的共享或排它锁),提高锁冲突检测性能,因为数据库引擎仅在表级检查意向锁,确定事务是否能安全获取表上的锁,而不需检查表中的每行或每页上的锁以确定事务是否可以锁定整个表。
SQL-Server中意向锁初识;
· 架构锁(Sch锁)
用于依赖表架构的操作。数据库引擎在编译和查询时使用架构稳定性 (Sch-S) 锁,在表DDL操作、某些DML操作时使用架构修改 (Sch-M) 锁。
· 键范围锁(Range锁)
在使用可序列化隔离级别时用于保护查询读取的行的范围,可以防止幻读(幻象插入/删除)。键范围锁放置在索引上,指定开始与结束的索引键值。
-=- 锁定对象 -=-
· 行级锁:行,InnoDB具有行锁定机制;
· 表级锁:表;
封锁粒度,Granularity,封锁对象的大小。封锁对象可以是逻辑单元,也可以是物理单元。封锁粒度与系统的并发度相关,封锁粒度越小,并发度越高,开销越大。选择封锁粒度时应该综合考虑封锁开销和并发度两个因素,查询优化器在编译执行计划时通常会选择正确的锁粒度。
3级封锁协议 - Locking Protocol
· 一级
♂ 内容:事务T读数据不加锁,修改数据对象A之前必须对其加X锁,直到事务结束才释放;
♂ 作用:解决"丢失修改"问题,保证事务T是可恢复的,但不能避免脏读和不可重复读;
· 二级
♂ 内容:一级协议X锁 + 事务T读取数据对象A之前必须对其加S锁,读完后立即释放;
♂ 作用:解决"丢失修改" + "脏读"问题,但不能避免不可重复读;
· 三级
♂ 内容:一级协议X锁 + 事务T读取数据对象A之前必须对其加S锁,直到事务结束才释放;
♂ 作用:解决"丢失修改" + "脏读" + "不可重复读"问题;
其中,事务结束包括commit和rollback。每一级协议的X锁均在事务结束后再释放,一级封锁协议读数据时并不加锁、仅关注事务对数据的修改,二、三级封锁协议关注事务对数据的读取。
参考:数据库并发问题及封锁协议; 封锁机制;
封锁后遗症
- 活锁 (Live Locks)
多个事务请求对同一数据封锁,某一事务总是处于等待的现象。事务T1封锁数据R,事务T2请求封锁R,于是T2等待。T3也请求封锁R,当T1释放对R的封锁后系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了对R的封锁后系统又批准了T4的请求,…,T2有可能永远等待。
方法:事务排队,先来先服务。
- 死锁 (Dead Locks)
也称阻塞,多个事务交错相互等待对方释放资源,循环依赖对方造成资源读写拥挤堵塞的情况,耗时耗资源。事务T1和T2都需要数据Rl和R2,当前Tl封锁数据R1、T2封锁数据R2;然后T1又请求封锁R2、T2又请求封锁Rl;因T2已封锁R2,故T1等待T2释放R2上的锁,因T1已封锁R1,故T2等待T1释放R1上的锁。由于Tl和T2都没有获得需要的全部数据,所以它们不会结束、只能互相等待。
注:区分正常阻塞和死锁的区别。
原因
- 系统资源不足,资源分配不当、竞争(不可剥夺)资源;
- 进程运行推进的顺序不合适;
- 信号量、临时资源利用不当;
必要条件:缺一不可
- 互斥条件:Mutual exclusion,在一段时间内某个资源只能被一个进程占用;
- 请求保持条件:Hold and wait,一个进程因请求资源而阻塞时,同时对已获得的资源保持不放;
- 不剥夺条件:No pre-emption,不能强行剥夺进程已经获得的资源;
- 循环等待条件:Circular wait,多个进程形成头尾相接的进程-资源环形链,循环等待资源;
方法:(防 + 治)结合;
数据库引擎的 死锁监视器 定期主动检测陷入死锁的任务。首先引入 安全状态 :系统能按某种顺序为每个进程分配所需资源并且依次地运行完毕,反之系统是不安全的。安全状态一定不会发生死锁,不安全状态不一定会发生死锁,死锁时系统一定处于不安全状态。
按同一顺序访问对象、缩短事务持锁时间(避免事务中的用户交互、使用较低的隔离级别、保持事务简短)、行版本控制、绑定连接均可以有效防止死锁问题,其中绑定会话有利于在同一台服务器上共享相同的事务和锁以实现多个会话之间协调操作。
■ 预防死锁
实质是破坏产生死锁的四个必要条件。实现简单,但是限制条件严格、资源利用率低,破坏系统的并行性、并发性,降低系统性能。
- 资源静态分配法:破坏请求和保持条件,进程所需资源一次性分配完毕;
- 可剥夺资源:破坏不可剥夺条件,系统让资源占有者主动释放资源;
■ 避免死锁
实质是系统允许前三个条件存在,但是在资源的动态分配过程中,预先计算资源分配的安全性以防止系统进入不安全状态(不会形成环形等待的封闭进程链),从而避免死锁。支持多进程并行执行。
- 有序资源分配法:破坏环路等待条件,系统为资源编号,进程按编号升序请求资源,释放则相反;
- 银行家算法:1968年由Dijkstra E.W提出,针对资源分配。限制条件少、资源利用率高,但开销大;
■ 检测和解除死锁
实质是允许系统在运行过程中发生死锁,但系统可及时检测出死锁的发生并精确确定与死锁有关的进程和资源,然后解除之。
[1]. 检测
- 首先为每个进程、每个资源指定唯一号码,建立进程等待表和资源分配表;
[2]. 解除
- 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程;
- 撤销进程:直接撤消死锁进程或撤销死锁优先级低的进程或撤消回滚代价最小的进程;
参考