Lock是一种机制,可以防止事务对共享数据的不正确的更新和不正确的修改数据结构,在维护数据的一致性和并发性中,它扮演了一个很重要的角色。
Lock行为概述
根据获得的锁的操作,数据库维护几种不同类型的锁。通常,数据库使用两种类型的锁:排它锁(exclusive locks)和共享锁(share locks)。在诸如行或表之类的资源上,只能获得一个排它锁,但是可以在单个资源上获得多个共享锁。
锁会影响读和写的交互。读是查询资源,而写是修改资源。下面总结了Oracle数据库读和写的锁行为:
- 当语句修改一行时,事务仅获得改行的锁,通过在行级别锁定表数据,数据库最小化对同样数据的争用;
- 如果一个事务正在修改一行,那么行锁会阻止其他事务同时对改行的修改;
- 读操作从不阻塞写操作,除了select…for update语句;
- 写操作从不阻塞读操作,当行正被写操作修改时,数据库使用undo数据来给读操作提供行的一致性视图;
Lock用途
在单用户数据库中,不需要锁,因为只有一个用户修改信息,然而,当多个用户访问和修改数据时,数据库必须提供一种方法来防止对相同数据的并发修改。Locks实现了下面的需求:
- 一致性(Consistency):会话正在查看和修改的数据不会被其他会话更改,直到该会话完成;
- 完整性(Integrity):数据和结构必须以正确的顺序反映对它们的所有更改;
Oracle数据库通过锁机制在事务中提供了数据的并发性、一致性和完整性,锁自动执行,不需要用户参与;
当执行SQL语句时,Oracle数据库自动获得必要的锁,例如,在会话修改数据前,会话必须锁定数据,该锁为会话提供了对数据的独占控制,以便其他事务在锁释放之前不会修改锁定的数据。
由于Oracle数据库的锁机制和事务控制密切相关,应用程序设计人员只需正确的定义事务,Oracle数据库会自动管理锁,用户不需显式的锁定任何资源,尽管Oracle数据库也能使用户手动锁定数据。
Lock模式
Oracle数据库自动使用最低限制适用级别,以提供最高程度的数据并发性,同时也提供了数据完整性。限制级别越低,数据越容易被其他用户访问,反之,限制级别越多,其他事务可获得的锁类型越受限。
Oracle数据库在多用户数据库中使用两种锁模式:
- 排他锁模式(Exclusive lock mode):该模式防止相关的资源共享,当事务修改数据时,将会获得排它锁。第一个单独锁定资源的事务是唯一可以改变该资源的事务,直到排它锁被释放;
- 共享锁模式(Share lock mode):该模式根据涉及的操作,相关的资源可以共享。读取数据的多个用户可以共享数据,持有共享锁可以防止需要排它锁的写操作并发访问,一些事务可以在同一资源上获得共享锁。
假想一个事务使用select … for update选择了单个表行,该事物获取了exclusive row lock和row share table lock,row lock允许其他会话修改除了已锁定行之外的任何行,而table lock则防止会话修改表的结构,因此,数据库允许尽可能多的语句执行。
Lock转换和升级
Oracle数据库在必要时执行锁转换。在锁转换中,数据库自动将较低限制的表锁转换为更高限制的。
例如:假设一个事务使用select … for update选择了单个行,然后更新锁定的行,在这种情况下,数据库自动将row share table lock转为row exclusive table lock,在该事务,它对已插入、修改和删除的所有行持有exclusive row locks,因为行锁是在最高限制程度下获得的,因此不需要或执行锁转换。
锁转换不同于锁升级,当多个锁在一个粒度级别(比如行)或数据库将锁提升到更高的粒度级别(比如表)时,就会发生锁升级。如果用户锁定表中许多行,那么一些数据库就会自动将row lock升级到单个表。锁的数量减少了,但是锁定的限制增加了。
Oracle数据库从不升级锁,锁升级容易引发死锁。
Lock持续时间
当事务不再需要资源时,Oracle数据库会自动释放锁。在大多数情况下,数据库持有在事务期间语句获得的锁,这些锁可以防止诸如脏读、丢失更新和来自并发事务的破坏性DDL等破坏性干扰。
当语句commit或rollback时,Oracle数据库会释放事务中语句获得的所有锁,当回滚到savepoint时,也会释放savepoint后获得的锁。然而,只有不等待之前已锁定资源的事务才能获取当前可用资源上的锁,正在等待的事务继续等待,直到所有之前的事务完全提交或回滚。
Locks和Deadlocks
死锁是指两个或多个用户在等待彼此锁定的数据,它阻止事务继续工作。
Oracle数据库自动探测死锁,并通过回滚包含在死锁中的语句来解决它们,释放一组冲突的行锁。数据库返回一个执行statement-level rollback的事务对应的消息。回滚的语句属于探测死锁的事务。通常,发出信号的事务应该被显式回滚,但它可以在等待后重试回滚语句。
当事务显式覆盖Oracle数据库的默认锁定时,最常发生死锁。由于Oracle数据库不会升级锁,也不使用read locks来查询,而是使用row-level的锁,死锁很少发生。
死锁演示:
会话1 |
会话2 |
说明 |
---|---|---|
SQL> update emp set sal=1000 2 where empno=7369; 1 row updated. |
SQL> update emp set sal=2000 2 where empno=7499; 1 row updated. |
会话1和2都拥有一个行级锁; |
SQL> update emp set sal=2500 2 where empno=7499; |
SQL> update emp set sal=1500 2 where empno=7369; |
会话彼此修改对方刚修改过的值,因资源互相被对方占用,故都出现等待; |
SQL> update emp set sal=2500 2 where empno=7499; update emp set sal=2500 * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource |
|
事务1检测到死锁,回滚并返回错误; |
SQL> select empno,ename,sal from emp 2 where empno in(7369,7499); EMPNO ENAME SAL ———- ———- ———- 7369 SMITH 1000 7499 ALLEN 1600 |
|
只回滚上一个事务; |
SQL> commit; Commit complete. |
|
事务1提交,并结束事务; |
|
1 row updated. |
事务2显示已更新一行; |
|
SQL> commit; Commit complete. SQL> select empno,ename,sal from emp 2 where empno in(7369,7499); EMPNO ENAME SAL ———- ———- ———- 7369 SMITH 1500 7499 ALLEN 2000 |
事务2提交,并结束事务; |
参考:官方文档