6.6 锁定机制和数据并发管理
锁有许多类型。大多数都是服务器内部的锁,例如串行化某些重要代码段的执行所需的锁,或保护某些内存结构的锁。OCA考试不包含这些,但在调整时,它们常常很重要。这里讨论的主题是在应用层上使用的锁,例如SQL执行时加上和释放的锁。这些锁自动或手动(假定开发人员的确需要这么做)应用于行或整个表。
借助于记录和表锁定机制,可以实现并发访问的串行化。Oracle数据库中的锁定是完全自动的。一般而言,只有在软件用质量较差的代码干扰自动锁定机制时,或业务分析出错导致会话冲突的业务模型时,才会引发某些问题。
6.6.1 共享锁与排他锁
Oracle数据库中锁定的标准级别保证了最大可能的并发级别。也就是说,如果某个会话正在更新一行,那么只有这行会被锁定。此外,锁定这一行只是为了防止其他会话对其进行更新,其他会话可以随时执行读取操作。只有在使用COMMIT或ROLLBACK命令结束事务之后,锁定才会被解除。这种锁定是一个“排他(exclusive)”锁:在指定记录上请求排他锁的第一个会话会得到这个锁定,其他请求对该记录进行写访问的会话则必须等待。虽然这一行已通过锁定会话进行了更新,但是对其进行读访问是允许的(而且经常会出现这种情况),并且这些读操作会涉及撤消数据的使用,从而确保读会话并不会看到任何未被提交的变更。
考点:
锁定的行不能由另一个会话更新或删除,但其他会话仍可以读取它们。
对于一行或一个完整表上的排他锁来说,每次只能有一个会话可以获得这个排他锁,不过许多会话可以同时获得同一对象上的“共享(shared)”锁。在一行上设置共享锁毫无意义,其原因在于锁定一行的唯一目的就是获得修改该行所需的排他锁。共享锁被置于整个表上,同时许多会话可以获得同一个表上的共享锁。在一个表上放置共享锁的目的是为了防止另一个会话获得这个表上的排他锁(在已存在共享锁的情况下无法再获得排他锁)。在表上放置排他锁时需要执行DDL语句。如果其他会话已经在一个表上放置了共享锁,就无法执行修改某个对象的语句(例如删除这个表的一列)。
为在行上执行DML语句,当前会话必须获取待更改行上的排他锁以及包含这些行的表上的共享锁。如果另一个会话已经获取了待更改行上的排他锁,那么当前会话将被挂起,直至使用COMMIT或ROLLBACK命令解除这些锁定。如果另一个会话已经获取了表上的共享锁以及其他行上的排他锁,那么就不存在任何问题。一个表上的排他锁也是允许的,但是,除非DDL语句要求这么做,默认锁定机制是不锁定整个表。
提示:
可以在整个表上施加独占锁,但这必须是特别申请的,程序员还需要有使用它的很好理由。
所有DML语句至少都需要两种锁:受影响记录上的排他锁,以及包含受影响记录的表上的共享锁。排他锁能够防止其他会话干预指定的行,而共享锁则能够阻止其他会话使用DDL语句修改表的定义。这两种锁定会被自动请求。如果某条DML语句在指定记录上无法获取所需的排他锁,那么会挂起这条语句,直至获得所需的排他锁。
执行DDL命令需要使用所涉及对象上的排他锁。只有在针对指定表的所有DML事务结束,且行上的排他锁以及表上的共享锁都被解除之后,才能获得执行DDL命令所需的排他锁。任何DDL语句所需的排他锁都是自动请求的。但是,如果无法获取所需的排他锁(通常是因为其他会话已经获得用于DML语句的共享锁),那么DDL语句会由于错误立即终止。
考点:
任何未提交的插入、更新或删除行的命令,都会导致表上的任何DDL立即失败。
6.6.2 排队机制
请求锁定需要排队。如果某个会话请求一个锁定,但由于其他会话已经锁定了指定的行或对象而无法获得所需的锁定,那么这个会话将会等待。此时,可能有多个会话都在等待访问相同的记录或对象,这种情况下,Oracle会跟踪这些会话请求锁定的顺序。当使用锁定的会话解除锁定时,下一个会话将获得授权,以此类推。这种机制称为“排队(enqueue)”机制。
如果不希望某个会话在无法获取锁定时进行排队,那么避免排队的唯一方式是使用SELECT... FOR UPDATE命令的WAIT或NOWAIT子句。因为SELECT语句并不需要任何锁定,所以普通的SELECT语句能够成功地执行,但是,DML语句则会被挂起。SELECT...FOR UPDATE命令会采用排他模式来选择和锁定记录。如果某行已被锁定,那么在锁定被释放之前,SELECT...FOR UPDATE语句会像DML语句一样进行排队并挂起会话。使用SELECT...FOR UPDATE NOWAIT或SELECT...FOR UPDATE WAIT <n>就可以避免挂起会话,其中<n>是以秒为单位的数值。使用SELECT...FOR UPDATE选项中的一个获得锁定后,我们就可在不必挂起会话的情况下执行DML命令。
提示:
可以将关键词SKIP LOCKED附加到SELECT FOR UPDATE语句后,这样将返回且锁定并未由另一会话锁定的行。
6.6.3 自动和手动锁定
无论执行什么DML语句,在执行过程中,会话会自动在表上施加一个共享锁,在受影响的行上施加独占锁。这个自动锁定对几乎所有操作都是完美的。它提供了可能性最大的并发(以及争用的可能性最低),不需要程序员输入什么内容。事务用COMMIT或ROLLBACK完成时,这些锁也会自动释放。
无论执行什么DDL语句,会话都会自动在整个对象上施加一个独占锁。这个锁在DDL语句的整个执行过程中都存在,在语句完成时,会自动释放。内部发生的是,DDL实际上是对数据字典中的表行执行的DML语句。如果能看到DROP TABLE命令的源代码,就会很清楚这一点。它会删除SYS.COL$表中的许多行(SYS.COL$表中的一行对应数据库中的每一列),再删除SYS.TAB$表中的一行(SYS.TAB$表中的一行对应于数据库中的每个表),其后执行COMMIT。大多数DDL语句执行起来很快,所以它们需要的表锁定通常不会被用户注意到。
手工锁定对象也是可行的。语法如下:
lock table table_name in mode_name mode;
有5种模式,每种都能或不能兼容另一个会话中某种模式的另一个锁定请求:
● ROW SHARE
● ROW EXCLUSIVE
● SHARE
● SHARE ROW EXCLUSIVE
● EXCLUSIVE
表6-5列出了锁定的兼容性。如果一个会话在表上施加了表6-5列出的锁定类型,另一个会话就能(Y)或者不能(N)施加第一列中的锁定类型。
表6-5 锁定类型
例如,如果会话在表上施加了ROW SHARE锁定,那么其他会话就可以施加除EXCLUSIVE之外的其他锁定类型。另一方面,如果会话在对象上施加了EXCLUSIVE锁定,那么其他会话就不能施加任何锁。ROW SHARE锁允许其他会话执行DML,但禁止其他会话在表上施加EXCLUSIVE锁。要删除表,需要EXCLUSIVE锁(且会自动请求)。因此ROW SHARE锁确保,表没有被另一个会话删除。实际上,可能手动施加的唯一锁定类型就是EXCLUSIVE。这会禁止任何其他会话对表执行任何DML。
只要会话对表执行DML,就会自动在表上施加ROW EXCLUSIVE模式的锁。因为它与采用ROW EXCLUSIVE模式锁的其他会话都不兼容,所以许多会话都可以并发地执行DML——只要它们不尝试更新同一行。