Oracle专家高级编程 第三章 封锁和并行性
锁定
锁是一种机制,管理共享资源的并行访问,也就是concurrent问题
当多个用户访问并更改数据或数据结构时,以适当的机制防止对相同的信息段进行修改
在Oracle中
- 事务处理是数据库的全部工作,
- 只要必须,就应该推迟提交,而不是迅速提交,在必须提交时提交,而不是必须提交前提交
- 只要需要,就应该保持对数据的锁定
- Oracle行级锁不包含开销
封锁问题
丢失更新
举例
- 用户1检索(查询)一行数据
- 用户2检索相同行
- 用户1修改那个行,更新且未提交
- 用户2修改那个行,更新且提交
悲观封锁(悲观锁)
在修改值之前就要发挥作用,即默认当前行数据一定会修改
行被锁之后,另一个用户修改行的进程中, 会返回"ORA-00054 资源忙(Resource Busy)"的错误,该进程被阻塞,需要等待用户完成工作
乐观封锁(乐观锁)
select for update nowait
- for update,排他锁(悲观锁)
- nowait,通知Oracle该sql语句采用非阻塞的方式修改或删除数据,如果发现涉及到的数据被占有(被锁),则立即通知Oracle该资源被占用,返回错误信息
使用悲观锁,当前正在修改的数据已经被check out了,没人能对其进行修改
行锁不会对其他用户的读取造成限制
阻塞
当一个会话请求另一个会话保持的资源时,会发生阻塞,它会一直挂起
4个DML语句会在数据库中阻塞
- INSERT
- UPDATE
- DELETE
- SELECT FOR UPDATE
被阻塞的插入
- 当用户拥有一个带有主键的表或其上有唯一约束时,两个会话同时试图用相同的值插入一行
- 有一个会话会被阻塞,直到另一个会话提交或回滚
- 前一种情况,被阻塞会话会收到关于值重复的错误;后一种情况,被阻塞会话紧接着执行
阻塞的update和delete
表示在代码中可能又了一个丢失更新的问题
试图更新一个其他用户已经在更新的行(已经被锁定)
通过select for update避免阻塞问题
- 验证在将数据查询以后没有更改(丢失更新预防)
- 锁定行(防止update或delete的阻塞)
死锁
Oracle死锁以后,会在服务器上创建一个跟踪文件,Oracle的死锁很少
对父表修改之后,Oracle将在子表上放置完整的表锁定
- 如果更新父表主键,子表没有索引会被锁定
- 如果删除了父表的行,整个子表也将被锁定
更新主键在RDMS中是一个巨大的"禁忌"
什么情况下不需要索引外码
- 不从父表删除
- 不更新父表的唯一/主键值
- 不从父表连接到子表
锁定扩大
当发生锁定扩大情况时,系统要降低锁定到粒度
例如,数据库系统将一个表的100个行级锁定转换为单一的表级锁定,锁定扩大用于锁资源稀少的数据库中频繁使用
Oracle不会扩大锁定,使用锁定转换,或锁定提升
尽可能在最低一级使用锁定,如果使用 select for update子句,会创建两个锁定:
- 一个锁定放在选择的行上
- 另一个锁在ROW SHARE TABLE锁上,放置在表上
所有针对表的其他语句是允许的,另一个会使用LOCK TABLE X IN SHARE MODE将表变为只读
锁定类型
DML锁定
select,insert,update和delete,指定行锁定或者表锁定
DDL锁定
create,alter,DDL锁定保护对象结构的定义
内部锁定和锁存器(latch)
Oracle保护内部数据结构的锁定,分析一个查询并生成优化的查询方案时,它将"锁存"库高速缓存器,是轻量的低级船行化设备
分布式锁定
OPS使用的锁,用于保证不同的节点资源保持相互之间的一致,分布式锁由数据库实例所持有
PCM(并行缓冲器管理,Parallel Cache Management)
保护多个实例之间缓冲存储器中高速缓存数据块的锁
DML锁定
DML锁用于保证一行在一段时间内只有一个用户进行修改
TX(事务)锁定
事务初始化其第一次更改结果时获得,且一直保持,直到事务执行提交(COMMIT)或回滚(ROLLBACK)
它用作一个排队机制,使其他会话等待该事务完成
修改或者select for update的每一行指向一个相关的TX锁定
Oracle锁定的处理方式
- 找出要锁定行的地址
- 到该行
- 锁定行,除非使用nowait选项
在Oracle锁定数据的行时,一个事务ID与包含数据的块相关
- 当锁定被释放时,那个事务ID被留下,此ID对事务来说是唯一的
- 代表回滚段的好、槽和序列号,放在保存行的块上,通知其他会话,我们占有这块资源
- 其他会话看到这个ID,会去查询事务的活动状态,中间有一个排队机制,请求锁定的会话将排队等待事务完成
物理属性参数
- INITRANS -- 结构初始、预分配额大小,随遇索引,默认是2,对于表,默认是1
- MAXTRANS -- 结构所能增长的最大尺寸,默认是255
默认情况下,每个块的生命从一个或两个事务槽开始,一个块可以拥有同时活动的事务数由MAXTRANS所限制
也被块上空间可用性所限制,即受到逻辑和物理上的限制
适当增加INITRNANS给预期的并行事务的数量在块上留出充足的空间
TM(DML 入队)锁定
保证更改表的内容时,表的结构不会被更改
如果已经更新了一个表,将得到在那个表上的一个TM锁定,放置另一个用户在表上运行DROP或ALTER命令
若试图在已经拥有TM锁定的表上执行DDL,将得到错误信息
ORA-00054:resource busy and acquire with NOWAIT specified
- 每一个事务中,只获得一个TX锁定,可以获得与所修改对象数量一样多的TM锁定
- TM的ID1列是DML锁定对象ID
系统中允许的TM锁定的总数是用户可配置的
它可以设置为0,不意味着用户的数据库变成只读的数据库,而是不允许DDL
使用alter table tablename,disable table lock命令批量删除TM锁
DDL锁定
DDL锁定在DDl操作期间自动针对对象放置,保护免于被其他会话更改
例如使用alter table命令时,DDL锁在DDL期间保持,这是由隐式提交(或提交/回滚对)包含的DDL语句完成
DDL总是提交,即使没有成功,可以使用自定义事务
3种类型的DDL锁定
独占的DDL锁定
防止其他会话自己获得DDL锁定或TM锁定,意味着在DDL操作期间查询一个表,但不能修改
共享DDL锁定
保护饮用对象的结构,防止被其他会话修改,允许对数据的修改
可打破的分析锁定
允许对象在一些对象上注册信任,如果执行针对该对象的DDL,Oracle检查已经注册依赖关系的对象列表,使其无效,不能防止DDL发生
- 关键字ONLINE修改实际建立索引,不使用DDL锁定,防止数据修改
- Oracle只是在表上获得一个低级的TM锁定
- 允许正常的DML操作
持久化DDL语句期间所做的修改,完成后将应用更新到心的索引上,提高了数据可用性
其他类型使用共享DDL锁定,
锁定器和内部锁定(入队)
锁存器和排队时轻量的序列化设备,用于协调多用户对共享的数据结构、对象和文件的访问
锁存器
锁存器时在极短时间内,例如修改内存中数据结构时间内保持的锁定,来保护内存结构
没有排队额锁存器的等待者,只有不断重试
Oracle使用原子指令进行锁存器操作,设置和释放指令是原子化的,运行效率高
万一一个锁存器持有者不正常的死掉,可以清除,由PMON执行
入队
一个更加复杂的串行设备,与锁存器的区别在于允许请求者排队等待资源
- 使用锁存器,请求者立刻被告知是否占有锁存器
- 使用入队,请求者被阻塞,直到实际获得为止
入队可以在不同等级获得,可以拥有许多"共享"锁定,用不同"共享能力"的锁
手动封锁和用户自定义锁定
手动封锁
select for update,最常用
lock table 比较粗糙,很少使用,锁表不锁行,如果写入大批更新,影响表中大部分行,可以使用
lock table in exclusive mode
可以保证不被用户阻塞
创建自己的锁定
借助DBMS_LOCK包
可以用这个包串行化对Oracle外部资源的访问
例如一个消息例程,文件在外部,Oracle不能协调试图同时修改它的用户,可以引入DBMS_LOCK包
对一个文件打开、写入或关闭操作前,文件进入独占模式前,请求一个指定的锁定,关闭文件后,手动释放锁定(有点像ReentrantLock)
并行控制
数据库提供的函数集,允许许多用户同时访问和修改数据,锁定的实现大概可以决定应用程序的并行程度
并行控制胜过锁定,多版本,写入不阻塞读取,
事务隔离级别
脏读
允许读一个没有提交的,或"脏"数据
非重复读
读者在时间T1读取一行,试图的时间T2再次读取行,该行可能已经更改或者消失
幻象读
如果时间T1执行了一个查询,并在时间T2再次执行,附加的行可能已经添加到数据库,影响结果
Oracle还支持只读模式
Read Uncommitted 隔离等级
允许脏读,Oracle不利用脏读,其目标在于提供"迎合"非阻塞读取的标准
Read Committed 隔离等级(最常用的隔离级别)
一个事务只能读取在事务开始前提交的数据,没有脏读
支持非阻塞读取
Repeatable Read 隔离等级
获得一致性答案,给定查询的结果必须与有关的时间点是一致的
- 在一个使用共享锁的数据库中,数据读取器将阻塞数据写入器,Oracle选择多版本的模型提供读一致性答案,降低并行程度
如果产生死锁,之后事务中的一个将成为牺牲者,被杀死
- 共享读取锁,数据的读取器和写入器经常互相死锁
Oracle使用多版本,拥有语句级的读取一致性,读取不阻塞写入,没有死锁
预防丢失更新
Repeatable Read 的普通用法就是为了预防丢失更新
Oracle中,需要Repeatable Read,不是用 select for update nowait,而是将隔离等级设置为Serializable
Serializable事务,使得我们从语句级获得的读取一致性扩展到事务,事务中执行的每一个查询答案固定在事务开始的时间点
Serializable 隔离等级
最具限制性的事务隔离等级,事务之间互不可见,隔离代价
ORA-08177:can't serialize access for this transaction
不论何时试图更新从事务开始以后已经更改的行,都会获得这个消息,什么时候适合用该隔离级别
- 只有很高的没有修改相同数据的可能性
- 与奥事务的读强一致性
- 进行短的事务
此方法的可伸缩行,足够运行全部的TPC-C(OLTP标准测试)
只读事务
类似于Serializable事务,区别在于不允许修改,不容易出现ORA-8177错误,该事务支持报告的需要
该事务可能会出现
ORA-1555 snapshot too old 错误,当其他人活跃的修改正在读取的数据,可能报此错误
睁开眼,书在面前 闭上眼,书在心里