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 错误,当其他人活跃的修改正在读取的数据,可能报此错误

posted @ 2021-04-15 10:24  YC-L  阅读(12)  评论(0编辑  收藏  举报