多用户数据库必须提供以下功能:
-
保证用户可以同时访问数据(数据并发性)
-
保证每个用户看到数据的一致的视图(数据一致性),包括可以看到用户自己的事务所做的更改, 和其他用户已提交的事务所做的更改。
另见:
多版本读一致性
数据库查询具有以下特征:
-
读一致查询
查询所返回的数据已提交的, 且关于某个单一时间点一致。
为说明脏读的问题, 假设一个事务更新某列的值,但不提交。第二个事务读取此已更新的脏 (未提交) 值。第一个会话回滚了事务,使该列仍具有其旧值,但第二个事务继续使用更新的值, 这会损坏数据库。脏读会破坏数据的完整性、 破坏外键、 和忽略唯一约束。 -
非阻塞查询
数据的读取者和写入者不会相互阻塞。
语句级读取一致性
- 在读提交隔离级别,该时间点是语句打开的时间。例如, 如果一个 SELECT 语句在 SCN 1000 时打开,则此语句一致于 SCN 1000。
- 在可串行化或只读事务隔离级别, 该时间点为事务开始的时间。例如, 如果一个事务开始于 SCN 1000, 且在该事务中有多个 SELECT 语句发生, 则每个语句都一致于 SCN 1000。
- 在闪回查询操作(SELECT ... AS OF) 中, SELECT 语句显式指定时间点。 例如,你可以查询某个表在上星期四下午 2 时的数据。
另见:
事务级读取一致性
读取一致性及撤消
另见:
-
"Undo Segments"学习undo存储
-
"Internal LOBs"了解 LOB 的读取一致性机制
读一致性:示例
这个示例显示了一个查询,在已提交读隔离级别使用撤销数据以提供语句级的读取一致性。
图 9-1 在已提交读隔离级别的读取一致性

Description of "Figure 9-1 Read Consistency in the Read Committed Isolation Level"
读取一致性和事务表
每个段块的块头包含一个感兴趣的事务列表(ITL)。
数据库使用 ITL 来确定当数据库开始修改块时是否某个事务还未提交。
ITL 中的条目描述了哪些事务有被锁定的行,以及块中的哪些行包含提交和未提交的更改。ITL 指向回滚段中的事务表,提供对数据库所做的更改的时间相关信息。
另见:
《Oracle Database SQL Language Reference》了解 INITRANS 参数
读一致性和延迟插入
一种称为延迟插入的特殊插入类型不使用标准的读一致性机制。
延迟插入使用MEMOPTIMIZE_WRITE提示插入到指定为 MEMOPTIMIZE FOR WRITE 的表中。数据库将这些插入缓冲到 large pool 中,而不是 buffer cache 中。数据库不使用 redo 和 undo 跟踪更改。相反,当Space Management Coordinator(SMCO)将缓冲区写入磁盘时,数据库将自动提交更改。不能回滚更改。
延迟插入与常规插入的重要区别在于:
-
驻留在应用程序(假定已提交的大池中)的数据可能会丢失。例如,在将更改保存到磁盘之前,数据库实例可能会失败,即使应用程序报告更改已保存。
-
不允许直接从内存中读取数据。在后台进程将更改写入磁盘之前,写入器无法读取自己的更改。在提交的更改被写到磁盘之前,没有任何读者可以看到它们。
为了避免客户机应用程序丢失数据,应该在写入大池之后在保存一份本地副本。客户端可以使用 DBMS_MEMOPTIMIZE 包跟踪对内存的写操作的持久性,而DBMS_MEMOPTIMIZE_ADMIN 包则强制数据库对磁盘进行写操作。
另见:
-
《Oracle Database SQL Language Reference》学习有关提示 MEMOPTIMIZE FOR WRITE 的更多信息
ANSI/ISO 事务隔离级别
-
脏读
一个事务读取了已被另一个事务写入、 但尚未提交的数据。
-
不可重复(模糊) 读
一个事务重新读取之前曾经读取过的数据,发现另一个已提交的事务已修改或删除了该数据。例如,用户查询某行,然后稍后又查询相同的行, 却发现数据已更改。 -
幻像读
一个事务重新运行满足某搜索条件的查询, 并返回一个行集, 发现另一个已提交的事务已插入了满足搜索条件的其他行。例如,一个事务查询雇员数目。五分钟后它执行相同的查询, 但现在人数却增加了一个,这是因为另一个用户为一名新员工插入了一条记录。 满足查询条件的数据比之前更多了,但与不可重复读不同,之前读取的数据不会变化。
表 9-1 在不同隔离级别下的可预防的读现象
隔离级别 | 脏读 | 不可重复读 | 幻像读 |
---|---|---|---|
未提交读 | 可能 | 可能 | 可能 |
已提交读 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
另见:
-
"Overview of Oracle Database Transaction Isolation Levels"了解已提交读、 可串行化、 和只读隔离级别
-
《Oracle Database SQL Language Reference》关于 Oracle 数据库与 SQL 标准的一致性的讨论
Oracle 数据库事务隔离级别概述
事务隔离级别的 ANSI 标准定义了在各个隔离级别所允许或必须防止的现象。
Oracle 数据库提供如下事务隔离级别:
另见:
-
《Oracle Database Development Guide》了解事务隔离级别的更多信息
-
《Oracle Database SQL Language Reference》和《Oracle Database PL/SQL Language Reference》了解 SET TRANSACTION ISOLATION LEVEL
读提交隔离级别
这个隔离级别是默认的,它适合于几乎不可能发生事务冲突的数据库环境。
在已提交读隔离级别中的读取一致性
另见:
在读提交事务中的写入冲突
阻止行修改的事务有时称为阻塞事务。读提交事务将等待阻塞事务结束并释放其行锁。
选项如下所示:
- 如果阻塞事务回滚, 正在等待的事务将继续并更改之前被锁定的行,就像另一个事务从未存在一样。
- 如果阻塞事务提交并释放了锁,则正在等待的事务将对这个刚被更新的行继续其预定更新。
下表显示了一个可能是可串行化的或已提交读的事务 1, 如何与另一个已提交读的事务 2 进行交互。表 9-2 显示了一个称为丢失更新的典型情况。事务 1 所作的更新不能在表中反映出来, 即使事务 1 已经提交它。制定一项策略以处理更新丢失是应用程序开发的一个重要部分。
表 9-2 在一个已提交读事务中的写入冲突和丢失更新
Session 1 | Session 2 | Explanation |
---|---|---|
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6200 Greene 9500 | No action. |
会话 1 查询 Banda、 Greene、和 Hintz 的薪金。 找不到名为 Hintz 的任何雇员。
|
SQL> UPDATE employees SET salary = 7000 WHERE last_name = 'Banda'; | No action. |
会话 1 开始一个事务 1, 更新 Banda 的薪金。 事务 1 的默认隔离级别是已提交读。
|
No action. | SQL> SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
会话 2 开始一个事务 2,并将隔离级别显式设置为已提交读。
|
No action. | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6200 Greene 9500 |
事务 2 查询 Banda、 Greene 和 Hintz 的薪金。 Oracle 数据库使用读取一致性, 显示事务 1 做出未提交更新前 Banda 的薪金。
|
No action. | SQL> UPDATE employees SET salary = 9900 WHERE last_name='Greene'; |
事务 2 成功更新 Greene 的薪金, 因为事务 1 只锁定了 Banda 所在的行
|
SQL> INSERT INTO employees (employee_id, last_name, email, hire_date, job_id) VALUES (210, 'Hintz', 'JHINTZ', SYSDATE, 'SH_CLERK'); | No action. |
事务 1 为雇员 Hintz 插入一行,但不提交。
|
No action. | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6200 Greene 9900 |
事务 2 查询 Banda、 Greene 和 Hintz 的薪金。
事务 2 看到它自己对 Greene 薪金的更新。 但事务 2 看不到由事务 1 对 Banda 薪金所做的未提交更新, 或为 Hintz 插入的新行。
|
No action. | SQL> UPDATE employees SET salary = 6300 WHERE last_name = 'Banda'; -- prompt does not return |
事务 2 试图更新当前被事务 1 锁定的 Banda 行, 这会产生一个写入冲突。事务 2 必须等待,直到事务 1 结束。
|
SQL> COMMIT; | No action. | 事务 1 提交其工作,以结束事务。 |
No action. | 1 row updated. SQL> |
现在 Banda 行上的锁释放了,所以事务 2 得以继续,并完成对 Banda 薪金的更新。
|
No action. | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6300 Greene 9900 Hintz |
事务 2 查询雇员 Banda、 Greene 和 Hintz 的薪酬。现在事务 1 已提交了所插入的 Hintz 行,并能被事务 2 看到。事务 2 也看到自己对 Banda 薪金的更新。
|
No action. | COMMIT; | 事务 2 提交其工作,以结束事务。 |
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6300 Greene 9900 Hintz | No action. |
会话 1 查询 Banda、 Greene、 Hintz 的行。 Banda 的薪金是 6300,这是事务 2 所作的更新。 事务 1 对 Banda 的薪金改至 7000 的更新现在"丢失"了。
|
另见:
-
"Use of Locks"了解丢失的更新
-
"Row Locks (TX)"了解数据库何时以及为什么获得行锁
可串行化隔离级别
-
大型数据库中只更新少数几行的短事务
-
两个并发事务将修改相同的行的可能性相对较低
-
较长时间运行的事务主要为只读事务
ORA-08177: Cannot serialize access for this transaction
-
将所执行的工作提交到该点
- 也许要先回滚到事务中之前建立的某保存点,然后执行一些其他额外的(不同) 语句
-
回滚整个事务
下表显示了一个可串行化事务与其它事务之间的交互。如果可串行化事务不会尝试更改由另一个事务在该可序列化事务开始后所提交的行, 就可以避免串行化访问问题。
表 9-3 串行化事务
Session 1 | Session 2 | 解释 |
---|---|---|
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6200 Greene 9500 | No action. |
会话 1 查询 Banda、 Greene、 和 Hintz 的薪酬。 找不到名为 Hintz 的任何雇员。
|
SQL> UPDATE employees SET salary = 7000 WHERE last_name='Banda'; | No action. |
会话 1 开始一个事务 1, 更新 Banda 的薪金。事务 1 的默认的隔离级别是已提交读。
|
No action. | SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
会话 2 开始一个事务 2,并将隔离级别显式设置为可序列化隔离级别。
|
No action. | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------ ---------- Banda 6200 Greene 9500 |
事务 2 查询 Banda、 Greene、 和 Hintz 的薪金。 Oracle 数据库使用读取一致性, 显示事务 1 做出未提交更新前 Banda 的薪金。
|
No action. | SQL> UPDATE employees SET salary = 9900 WHERE last_name = 'Greene'; |
事务 2 成功更新 Greene 的薪金,因为只有 Banda 行被锁定。
|
SQL> INSERT INTO employees (employee_id, last_name, email, hire_date, job_id) VALUES (210, 'Hintz', 'JHINTZ', SYSDATE,'SH_CLERK'); | No action. | 事务 1 为雇员 Hintz 插入一行。 |
SQL> COMMIT; | No action. | 事务 1 提交其工作,以结束事务。 |
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 7000 Greene 9500 Hintz | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- --------- Banda 6200 Greene 9900 |
会话 1 查询雇员 Banda、 Greene、 和 Hintz 的薪金, 只能看到由事务 1 提交的更改。 会话 1 不能看见会话 2 对 Greene 所做的未提交更新。
事务 2 查询雇员 Banda、 Greene、 和 Hintz 的薪金。 Oracle 数据库读取一致性确保由事务 1 所做的插入 Hintz 行和对 Banda 行的更新,对事务 2 不可见。事务 2 只看到自己对 Banda 薪金的更新。
|
No action. | COMMIT; | 事务 2 提交其工作, 结束该事务。 |
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 7000 Greene 9900 Hintz | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- --------- Banda 7000 Greene 9900 Hintz |
两个会话分别查询雇员 Banda, Greene, 和 Hintz 的薪金。每个会话将看到事务 1 和事务 2 作出的所有已提交更改。
|
SQL> UPDATE employees SET salary = 7100 WHERE last_name = 'Hintz'; | No action. |
会话 1 更新 Hintz 的薪金, 开始事务 3。 事务 3 的默认隔离级别是已提交读。
|
No action. | SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
会话 2 开始事务 4,并将其设置为可串行化隔离级别。
|
No action. | SQL> UPDATE employees SET salary = 7200 WHERE last_name = 'Hintz'; -- prompt does not return |
事务 4 尝试更新 Hintz 的薪金,但被阻塞,因为事务 3 锁定了 Hintz 行在事务队列中, 事务 4 排在事务 3 的后面。
|
SQL> COMMIT; | No action. |
事务 3 提交了它对 Hintz 工资的更新,以结束事务。
|
No action. | UPDATE employees SET salary = 7200 WHERE last_name = 'Hintz' * ERROR at line 1: ORA-08177: can't serialize access for this transaction |
结束事务 3 的提交导致事务 4 对 Hintz 更新失败,导致 ORA-08177 错误。 该错误之所以发生,是因为事务 3 是在事务 4 开始后提交的
|
No action. | SQL> ROLLBACK; | 会话 2 回滚事务 4,结束该事务。 |
No action. | SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
会话 2 开始事务 5 ,并将其设置为可串行化隔离级别。
|
No action. | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 7000 Greene 9500 Hintz 7100 |
事务 5 查询 Banda、 Greene、 和 Hintz 的薪金。事务 3 所提交的对 Hintz 薪酬的更新是可见的。
|
No action. | SQL> UPDATE employees SET salary = 7200 WHERE last_name='Hintz'; 1 row updated. |
事务 5 将 Hintz 的薪金更新为一个不同值。因为事务 3 对 Hintz 的更新是在事务 5 开始之前提交的,也就避免了序列化的访问问题。
|
No action. | SQL> COMMIT; |
会话 2 提交了更新,未遇到任何问题,并结束事务。
|
只读隔离级别
另见:
-
《Oracle Database Administrator’s Guide》了解如何设置撤消保留期
数据库锁定机制概述
锁是一种防止破坏性交互的机制。
当访问共享数据的事务之间不正确地更新数据或不正确地更改基础数据结构时,交互是破坏性的。锁在维护数据库并发性和一致性当中扮演着一个关键的角色。
锁定行为总结
数据库维护几种不同类型的锁,这取决于获取锁的操作。
-
一行只有在被某个写入者修改时,才被锁定。
当一个语句更新某行时,事务只需要获取在该行上的锁。通过在行级别锁定表数据,数据库最小化对同一数据的争用。在正常情况下,数据库不会将行锁升级到块级或表级。 -
一行的写入者,会阻塞在同一行上的并发写入者。
如果一个事务正在修改某行, 则行锁可防止不同的事务同时修改同一行。 -
一个读取者永远不会阻塞一个写入者。
由于行的读取者不会将它锁定, 所以一个写入者可以修改该行。唯一的例外是 SELECT ... FOR UPDATE 语句,它是一种特殊类型的 SELECT 语句, 的确会锁定它正在读取的行。 -
一个写入者绝不会阻塞一个读取者。
当一个行正在被某个写入者更改时,数据库使用撤销数据向读取者提供一个该行的一致视图。
另见:
-
《Oracle Database SQL Language Reference》了解 SELECT ... FOR UPDATE
-
《Oracle Database Administrator’s Guide》了解与不确定分布式事务关联的等待
使用锁
锁实现了以下重要的数据库需求:
-
一致性
一个会话正在查看或更改的数据不能被其它会话更改,直到用户会话结束。 -
完整性
数据和结构必须按正确的顺序反映对他们所做的所有更改。
UPDATE employees
SET email = ?, phone_number = ?
WHERE employee_id = ?
AND email = ?
AND phone_number = ?
表 9-4 行锁定实例
T | Session 1 | Session 2 | 解释 |
---|---|---|---|
t0 | SELECT employee_id as ID, email, phone_number FROM hr.employees WHERE last_name='Himuro'; ID EMAIL PHONE_NUMBER --- ------- ------------ 118 GHIMURO 515.127.4565 |
在会话 1 中, 用户 hr1 查询在表 hr.employees 中的 Himuro 的记录,并显示其员工 ID(118)、 电子邮件(GHIMURO) 、 和电话号码 (515.127.4565) 等属性。
| |
t1 | SELECT employee_id as ID, email, phone_number FROM hr.employees WHERE last_name='Himuro'; ID EMAIL PHONE_NUMBER --- ------- ------------ 118 GHIMURO 515.127.4565 |
在会话 2 中, 用户 hr2 查询在表 hr.employees 中的 Himuro 的记录,并显示其 员工 ID (118)、(GHIMURO) 和电话号码(515.127.4565) 等属性。
| |
t2 | UPDATE hr.employees SET phone_number='515.555.1234' WHERE employee_id=118 AND email='GHIMURO' AND phone_number = '515.127.4565'; 1 row updated. |
在会话 1 中, 用户 hr1 对 GHIMURO 所在行, 更新其电话号码为 515.555.1234, 这会在该行上获取一个行锁。
| |
t3 | UPDATE hr.employees SET phone_number='515.555.1235' WHERE employee_id=118 AND email='GHIMURO' AND phone_number = '515.127.4565'; -- SQL*Plus does not show -- a row updated message or -- return the prompt. |
在会话 2 中, 用户 hr2 尝试更新相同的行,但被阻塞, 因为 hr1 当前正在处理该行。
hr2 的更新尝试几乎与 hr1 的更新同时发生。
| |
t4 | COMMIT; Commit complete. |
在会话 1 中,用户 hr1 提交其事务。
该提交使对 Himuro 的更改持久化, 这也就取消了对一直在等待的会话 2 的阻塞。
| |
t5 | 0 rows updated. |
在会话 2 中, 用户 hr2 发现 GHIMURO 行被修改,且不再匹配其谓词。
由于该谓词不匹配,会话 2 未能更新任何记录。
| |
t6 | UPDATE hr.employees SET phone_number='515.555.1235' WHERE employee_id=118 AND email='GHIMURO' AND phone_number='515.555.1234'; 1 row updated. |
在会话 1 中, hr1 用户发现它错误地更新了 GHIMURO 行的电话号码。 该用户启动一个新事务,并将该行的电话号码更新为 515.555.1235, 这又将锁定 GHIMURO 行。
| |
t7 | SELECT employee_id as ID, email, phone_number FROM hr.employees WHERE last_name='Himuro'; ID EMAIL PHONE_NUMBER --- ------- ------------ 118 GHIMURO 515.555.1234 |
在会话 2 中, 用户 hr2 查询表 hr.employees 中的 Himuro 的记录。记录显示由会话 1 在 t4 时刻对电话号码所做的已提交更新。Oracle 数据库读取一致性确保会话 2 不会看到会话 1 在 t6 时刻所做的未提交更改。
| |
t8 | UPDATE hr.employees SET phone_number='515.555.1235 WHERE employee_id=118 AND email='GHIMURO' AND phone_number = '515.555.1234'; -- SQL*Plus does not show -- a row updated message or -- return the prompt. |
在会话 2 中, 用户 hr2 尝试更新相同的行,但被阻塞, 因为 hr1 当前正在处理该行。
| |
t9 | ROLLBACK; Rollback complete. |
在会话 1 中, hr1 用户回滚其事务并结束之。
| |
t10 | 1 row updated. |
在会话 2 中,因为会话 1 的更新被回滚,所以这次对电话号码的更新成功了。 GHIMURO 行与它的谓词相匹配,因此更新成功了。
| |
t11 | COMMIT; Commit complete. | 会话 2 提交其更新, 以结束事务。 |
以下各节解释了理解数据库如何实现数据并发很重要的概念。
另见:
《Oracle Database PL/SQL Packages and Types Reference》了解 OWA_OPT_LOCK 软件包, 其中包含了有助于防止丢失更新的子程序
锁模式
Oracle 数据库在多用户数据库中使用两种锁定模式:
-
独占锁模式
此模式可防止相关资源被共享。当一个事务修改数据时, 获取一个独占锁。直到独占锁被释放之前, 第一个以独占方式锁定资源的事务是唯一可以更改资源的事务, 。 -
共享锁模式
取决于所涉及的操作, 此模式允许相关资源被共享。读取数据的多个用户可以共享数据,并持有共享锁,以防止企图获取独占锁的写入者并发访问。多个事务可以同时获取在同一资源上的共享锁。
锁转换和锁升级
Oracle 数据库在必要时执行锁转换。
Oracle 数据库永远不会升级锁。锁升级极大地增加了死锁的可能性。假定一个系统尝试升级事务 1 中的锁,但因为事务 2 持有该锁,故不能成功。如果事务 2 在它可以继续操作之前也需要在相同的数据上进行锁升级, 则将发生一个死锁。
锁持续时间
当某些事件发生,使事务不再需要资源时, Oracle 数据库会自动释放锁。
通常,数据库持有语句所获取的锁,直至该语句所在事务的整个持续期间结束。这些锁可以防止破坏性干扰, 如多个并发事务中的脏读、更新丢失、 和破坏性 DDL。
另见:
-
"Table 10-3"这显示了事务等待行为
-
"Overview of User-Defined Locks"要了解更多关于DBMS_LOCK的信息
锁和死锁
Table 9-5 说明了在死锁中的两个事务。
表 9-5 死锁示例
T | Session 1 | Session 2 | 解释 |
---|---|---|---|
t0 | SQL> UPDATE employees SET salary = salary*1.1 WHERE employee_id = 100; 1 row updated. | SQL> UPDATE employees SET salary = salary*1.1 WHERE employee_id = 200; 1 row updated. |
会话 1 启动事务 1,并更新雇员 100 的薪酬。 会话 2 启动事务 2,并更新雇员 200 的薪酬。没有问题存在,因为每个事务只锁定它会尝试更新的行。
|
t1 | SQL> UPDATE employees SET salary = salary*1.1 WHERE employee_id = 200; -- prompt does not return | SQL> UPDATE employees SET salary = salary*1.1 WHERE employee_id = 100; -- prompt does not return |
事务 1 试图更新员工 200 的行, 这当前正由事务 2 锁定。 事务 2 试图更新雇员 100 的行, 这当前正由事务 1 锁定。
死锁发生了, 因为任何一个事务都不能获取其得以继续或终止的资源。无论每个事务等待多久,冲突锁依然被对方持有。
|
t2 | UPDATE employees * ERROR at line 1: ORA-00060: deadlock detected while waiting for resource SQL>``` |
事务 1 发出死锁信号,并回滚在 t1 时刻发出的 UPDATE 语句。但是,在 t0 时刻所做的更新不会回滚。 会话 1 返回到提示符。
| |
t3 | SQL> COMMIT; Commit complete. |
会话提交了 t0 时刻所做的更新,以结束事务 1。 在 t1 时刻尝试的更新不成功,未能提交。
| |
t4 | 1 row updated. SQL> |
执行事务 2 在 t1 时刻所做的更新,之前被事务 1 阻塞,现在得以执行。并返回提示符。
| |
t5 | SQL> COMMIT; Commit complete. |
会话 2 提交其在 t0 和 t1 时刻所做的更新,结束事务 2。
|
另见:
-
《Oracle Database Development Guide》了解当您显式锁定表时, 如何处理死锁
自动锁的概述
Oracle数据库锁分为如下表所示的类别。
表 9-6 锁的类别
锁 | 描述 | 参阅 |
---|---|---|
DML 锁 | 保护数据。例如, 表锁锁定整个表,而行锁锁定所选的行。 | |
DDL 锁 | 保护模式对象的结构 ——例如, 表和视图的数据字典定义。 | |
系统锁 |
保护内部数据库结构, 如数据文件。闩锁、 互斥体、 和内部锁是完全自动的。
|
DML 锁
DML 锁,也称为数据锁,确保由多个用户并发访问的数据的完整性。
DML 语句自动获取下列类型的锁:
行锁(TX)
行锁和并发
这个场景说明了 Oracle 数据库如何在并发事务中使用行锁。
表 9-7 数据并发示例
T | Session 1 | Session 2 | Session 3 | Explanation |
---|---|---|---|---|
t0 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 600 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 600 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 600 | 三个不同的会话同时查询 ID 为 100 和 101 的雇员的薪金。每个查询所返回的结果是相同的。 |
t1 | UPDATE hr.employees SET salary = salary+100 WHERE employee_id=100; |
会话 1 更新雇员 100 的薪金,但不提交。对此更新,写入者只需要对更新的行获得一个行级锁, 从而防止其他写入者修改这一行。
| ||
t2 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 612 101 600 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 600 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 600 |
每个会话同时发出原来的查询。 会话 1 显示了 t1 时刻的薪金更新结果 612。 会话 2 和会话 3 中的读取者立即返回行,并不等待会话 1 结束其事务。 数据库使用多版本读取一致性将薪金显示为会话 1 更改它之前的样子。
|
t3 | UPDATE hr.employee SET salary = salary+100 WHERE employee_id=101; |
会话 2 更新雇员 101 的工资,但不提交事务。 对此更新,写入者只需要对更新的行获得一个行级锁, 从而防止其他写入者修改这一行。
| ||
t4 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 612 101 600 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 700 | SELECT employee_id, salary FROM employees WHERE employee_id IN ( 100, 101 ); EMPLOYEE_ID SALARY ----------- ------ 100 512 101 600 |
每个会话同时发出原来的查询。 会话 1 显示了 t1 时刻的薪金更新结果 612, 而不显示在会话 2 中为员工 101 所做的的薪金更新。 会话 2 中的读取者显示在会话 2 中所做的薪金更新,但不显示会话 1 中所做的薪金更新。在会话 3 中的读取者使用读
取一致性来显示会话 1 和 2 的修改之前的薪金。
|
另见:
行锁存储
当事务结束时, 事务 ID 仍然保留在块头中。如果一个不同的事务想要修改某行,则它使用该事务 ID 来确定锁是否处于活动状态。如果锁是活动的,则会话要求该锁被释放时得到通知。否则,事务获取该锁。
另见:
-
"Overview of Data Blocks"要了解有关数据块头的更多信息
-
《Oracle Database Reference》了解 V$TRANSACTION
表锁(TM)
表锁可能以下列模式之一持有:
-
行共享 (RS)
这种锁也被称为子共享表锁 (SS) , 表示在表上持有锁的事务在表中有被锁定的行, 并打算更新它们。行共享锁是限制最少的表级锁模式,提供在表上最高程度的并发性。 -
行独占表锁 (RX)
这种锁也被称为子独占表锁(SX) , 通常表示持有锁的事务已更新了表行或发出了 SELECT ... FOR UPDATE。一个 SX 锁允许其他事务并发地查询、 插入、 更新、 删除、 或锁定在同一个表中的行。因此, SX 锁允许多个事务对同一个表同时获得 SX和子共享表锁。 -
共享表锁(S)
由某个事务拥有的共享表锁允许其他事务查询(而不使用 SELECT ... FOR UPDATE),但是更新操作只能在仅有单个事务持有共享表锁时才允许。因为可能有多个事务同时持有共享表锁,所以持有此锁不足以确保一个事务可以修改该表。 -
共享行独占表锁 (SRX)
这种锁也称为共享子独占表锁(SSX) , 比共享表锁的限制性更强。一次只能有一个事务可以获取给定的表上的 SSX 锁。 由某个事务拥有的 SSX 锁允许其他事务查询该表 (除 SELECT ... FOR UPDATE) 但不能更新该表。 -
独占表锁(X)
这种锁是最严格的锁, 禁止其他事务执行任何类型的 DML 语句, 或在表上放置任何类型的锁。
另见:
锁和外键
Oracle 数据库将与外键相关的父键的并发控制最大化。
锁定和未索引外键
当子表的外键列没有索引存在,且一个会话修改父表的主键(例如,删除行或修改主键属性)或合并行父表时,数据库将为子表获取一个全表锁。
当下列两个条件成立时, 数据库在子表上获取一个全表锁定:
-
在子表的外键列上没有索引存在。
- 会话修改了父表中的主键 (例如,删除了行或修改了主键属性)或将行合并到父表。
锁和索引外键
数据库不会获取子表上的全表锁定,当子表中的外键列已被索引,且会话修改了父表中的主键 (例如,删除了行或修改了主键属性)或将行合并到父表时。
图 9-4 显示一个具有索引列 department_id 的子表 employees。事务从 departments 表中删除部门 280。此删除操作不会像 "Locks and Unindexed Foreign Keys"场景所描述那样,导致数据库在 employees 表上获取全表锁定。
DDL 锁
独占 DDL 锁
独占 DDL 锁可防止其他会话获取 DDL 或 DML 锁。
另见:
"Share DDL Locks" 描述不需要专用锁来防止破坏性干扰的情况
共享 DDL 锁
例如, 当 CREATE PROCEDURE 语句运行时, 所在事务将为所有被引用的表获取共享 DDL 锁。其他事务可以同时创建引用相同表的过程, 并在相同的表上同时获得共享 DDL 锁,但没有任何事务能在任何被引用表上获取独占 DDL 锁。
可中断的解析锁
另见:
系统锁
闩锁
-
被多个会话同时修改
-
正在被一个会话读取时,又被另一个会话修改
-
正在被访问时,其内存被释放(换出)
另见:
-
《Oracle Database Reference》了解 V$LATCH
-
《Oracle Database Performance Tuning Guide》了解等待事件统计信息
互斥体
互斥体提供以下几个优点:
-
互斥体可以减少发生争用的可能性。
由于闩锁保护多个对象, 当多个进程试图同时访问这些对象的任何一个时,它可能成为一个瓶颈。 而互斥体仅仅串行化对单个对象的访问,而不是一组对象, 因此互斥体提高了可用性。 -
互斥体比闩锁消耗更少的内存。
-
在共享模式下, 互斥体允许被多个会话并发引用。
内部锁
内部锁是比闩锁和互斥体更高级、 更复杂的机制,并用于各种目的。
-
字典缓存锁
这些锁的持续时间很短, 当字典缓存中的条目正在被修改或使用时被持有。他们保证正在被解析的语句不会看到不一致的对象定义。 字典缓存锁可以是共享的或独占的。 共享锁在解析完成后被释放,而独占锁在 DDL 操作完成时释放。 -
文件和日志管理锁
这些锁保护各种文件。例如, 一种内部锁保护控制文件,以便一次只有一个进程可以对其进行更改。 而另一种锁用于协调联机重做日志文件的使用和归档。数据文件被锁定, 确保数据库被多个实例以共享模式装载, 或以独占模式被单个实例装载。因为文件和日志锁表示文件的状态,这些锁必要时会被持有较长一段时间。 -
表空间和撤销段锁
这些锁保护的表空间和撤销段。例如, 访问数据库的所有实例对一个表空间是否处于联机或脱机必须保持一致。 撤销段被锁定, 以便只能有一个数据库实例可以写入该段。
另见:
手动数据锁概述
您可以手动覆盖 Oracle 数据库的默认锁定机制。
-
应用程序需要事务级读取一致性或可重复读取。
在这种情况下, 查询在整个事务持续期间必须产生一致的数据, 但不反映其他事务所做的更改。通过使用显式锁定、 只读事务、 可串行化事务、 或覆盖默认锁定,可以实现事务级别的读取一致性。 - 应用程序需要事务对资源具有独占访问权限,以便该事务不必等待其他事务完成就可以继续。
-
SET TRANSACTION ISOLATION LEVEL 语句
- LOCK TABLE 语句 (锁定一张表,或与视图一起使用时则锁定其基表)
-
SELECT ... FOR UPDATE 语句
由上述语句获取的锁在事务结束后被释放, 或回滚到保存点后释放。
另见:
-
《Oracle Database SQL Language Reference》关于 LOCK TABLE 和 SELECT 的描述
-
《Oracle Database Development Guide》了解如何手动锁定表
用户定义的锁的概述
-
请求一个特定类型的锁
- 给锁起一个唯一的名称,以与(同一实例或不同实例中) 另一个过程中的某个锁相区别
-
更改锁类型
-
释放锁
另见:
-
《Oracle Database Development Guide》关于 Oracle 数据库锁定管理服务的详细信息
-
《Oracle Database PL/SQL Packages and Types Reference》有关 DBMS_LOCK 的信息