12 Data Concurrency and Consistency(12 数据并发与一致性)
本章阐述 Oracle AI Database 如何在多用户数据库环境中维护数据一致性。
- Introduction to Data Concurrency and Consistency(数据并发与一致性简介)
在单用户数据库中,用户修改数据时无需顾虑其他用户同时修改相同数据。然而,在多用户数据库中,多个并发事务中的语句可能会更新相同的数据。并发执行的事务必须产生有意义且一致的结果。 - Overview of Oracle AI Database Transaction Isolation Levels(Oracle AI Database 事务隔离级别概述)
事务隔离级别的 ANSI 标准是根据每个隔离级别所允许或阻止的现象来定义的。 - Overview of the Oracle AI Database Locking Mechanism(Oracle AI Database 锁机制概述)
锁是一种防止破坏性交互的机制。 - Overview of Automatic Locks(自动锁概述)
Oracle AI Database 会自动代表事务锁定资源,以防止其他事务执行需要独占访问同一资源的操作。 - Overview of Manual Data Locks(手动数据锁概述)
您可以手动覆盖 Oracle AI Database 的默认锁机制。 - Overview of User-Defined Locks(用户自定义锁概述)
借助 Oracle AI Database 锁管理服务,您可以为特定应用程序定义自己的锁。
Introduction to Data Concurrency and Consistency(数据并发与一致性简介)
在单用户数据库中,用户修改数据时无需顾虑其他用户同时修改相同数据。然而,在多用户数据库中,多个并发事务中的语句可能会更新相同的数据。并发执行的事务必须产生有意义且一致的结果。
多用户数据库必须提供以下保障:
- 确保用户能够同时访问数据(数据并发性)
- 确保每个用户都能看到数据的一致视图(数据一致性),包括用户自身事务所做的更改以及其他用户已提交事务所做的可见更改
为了描述事务并发运行时的行为一致性,数据库研究人员定义了一种称为可串行化的事务隔离模型。可串行化事务的操作环境使其看起来好像没有其他用户在修改数据库中的数据。
虽然这种事务间的隔离程度通常是理想的,但以可串行化模式运行许多应用程序可能会严重损害应用程序的吞吐量。完全隔离并发运行的事务可能意味着一个事务无法向另一个事务正在查询的表中执行插入操作。简而言之,现实世界的考虑通常需要在完美的事务隔离和性能之间做出权衡。
Oracle AI Database 通过使用多版本一致性模型和各种类型的锁及事务来维护数据一致性。通过这种方式,数据库可以向多个并发用户呈现数据视图,每个视图都一致到某个时间点。由于不同版本的数据块可以同时存在,事务可以读取在查询所需时间点已提交的数据版本,并返回一致到单个时间点的查询结果。
- Multiversion Read Consistency(多版本读一致性)
在 Oracle AI Database 中,多版本化是指能够同时物化多个数据版本的能力。Oracle AI Database 维护多版本读一致性。 - Locking Mechanisms(锁机制)
通常,多用户数据库使用某种形式的数据锁来解决与数据并发性、一致性和完整性相关的问题。 - ANSI/ISO Transaction Isolation Levels(ANSI/ISO 事务隔离级别)
已被 ANSI 和 ISO/IEC 采纳的 SQL 标准定义了四个事务隔离级别。这些级别对事务处理吞吐量的影响程度各不相同。
另请参见
- "Data Integrity"(数据完整性)和 "Transactions"(事务)
Multiversion Read Consistency(多版本读一致性)
在 Oracle AI Database 中,多版本化 是指能够同时物化多个数据版本的能力。Oracle AI Database 维护多版本读一致性。
Oracle 数据库的查询具有以下特征:
-
读一致性查询
查询返回的数据是已提交的,并且一致到单个时间点。
注意
Oracle AI Database 绝不 允许脏读,即一个事务读取另一个事务中未提交数据的情况。
为说明脏读的问题,假设一个事务更新了某列的值但未提交。第二个事务读取了更新后的、脏的(未提交的)值。第一个会话回滚了事务,该列恢复为旧值,但第二个事务却继续使用更新后的值,导致数据库损坏。脏读会损害数据完整性,违反外键约束,并忽略唯一约束。
-
非阻塞查询
数据的读取者和写入者不会相互阻塞。
-
Statement-Level Read Consistency(语句级读一致性)
Oracle AI Database 始终强制实行语句级读一致性,这保证了单个查询返回的数据是已提交的,并且一致到单个时间点。
-
Transaction-Level Read Consistency(事务级读一致性)
Oracle AI Database 还可以为事务中的所有查询提供读一致性,即所谓的事务级读一致性。
-
Read Consistency and Undo Segments(读一致性与撤销段)
为了管理多版本读一致性模型,当表被同时查询和更新时,数据库必须创建一组读一致性的数据。
-
Read Consistency and Deferred Inserts(读一致性与延迟插入)
一种称为延迟插入的特殊插入类型不使用标准的读一致性机制。
另请参见:"Summary of Locking Behavior"(锁行为摘要)
Statement-Level Read Consistency(语句级读一致性)
Oracle AI Database 始终强制实行语句级读一致性,这保证了单个查询返回的数据是已提交的,并且一致到单个时间点。
单个 SQL 语句所一致到的时间点取决于事务隔离级别和查询的性质:
- 在读已提交 隔离级别中,该点是语句 打开时的时间。例如,如果一个
SELECT语句在 SCN 1000 时打开,那么该语句将一致到 SCN 1000。 - 在可串行化 或只读 事务中,该点是事务 开始时的时间。例如,如果一个事务在 SCN 1000 时开始,并且在此事务中有多个
SELECT语句,那么每个语句都一致到 SCN 1000。 - 在闪回查询操作(
SELECT ... AS OF)中,SELECT语句显式地指定了时间点。例如,您可以查询表在上周四下午 2 点时的状态。
另请参见
- 《Oracle AI Database Development Guide》(《Oracle AI Database 开发指南》),了解闪回查询
Transaction-Level Read Consistency(事务级读一致性)
Oracle AI Database 还可以为事务中的所有查询提供读一致性,即所谓的事务级读一致性。
在这种情况下,事务中的每条语句看到的数据都来自同一个时间点。该时间点即事务开始时的时间。
可串行化事务所做的查询可以看到该事务本身所做的更改。例如,一个更新了 employees 然后又查询 employees 的事务将看到这些更新。事务级读一致性产生可重复读 ,并且不会使查询暴露于幻读。
Read Consistency and Undo Segments(读一致性与撤销段)
为了管理多版本读一致性模型,当表被同时查询和更新时,数据库必须创建一组读一致性的数据。
Oracle AI Database 通过撤销数据来实现读一致性。
每当用户修改数据时,Oracle AI Database 都会创建撤销条目,并将其写入撤销段。撤销段包含已被未提交或最近已提交事务更改的数据的旧值。因此,同一数据的多个版本(都处于不同的时间点)可以存在于数据库中。数据库可以使用不同时间点的数据快照来提供数据的读一致性视图,并实现非阻塞查询。
在单实例和 Oracle Real Application Clusters (Oracle RAC) 环境中,读一致性均得到保证。Oracle RAC 使用一种称为缓存融合的缓存间块传输机制,将数据块的读一致性映像从一个数据库实例传输到另一个实例。
-
Read Consistency: Example(读一致性:示例)
此示例展示了一个查询,该查询使用撤销数据在读已提交隔离级别中提供语句级读一致性。
-
Read Consistency and Interested Transaction Lists(读一致性与关注事务列表)
每个段块的块头都包含一个关注事务列表(ITL)。
另请参见
- "Undo Segments"(撤销段),了解撤销存储
- 《Oracle AI Database SecureFiles and Large Objects Developer's Guide》(《Oracle AI Database SecureFiles 和大型对象开发人员指南》),了解 LOB 的读一致性机制
Read Consistency: Example(读一致性:示例)
此示例展示了一个查询,该查询使用撤销数据在读已提交隔离级别中提供语句级读一致性。
Figure 12-1 Read Consistency in the Read Committed Isolation Level(图12-1 读已提交隔离级别中的读一致性)

当数据库代表查询检索数据块时,数据库会确保每个块中的数据都反映了查询开始时的块内容。数据库会根据需要回滚对块的更改,以将块重建到查询开始处理时的时间点。
数据库使用一种称为SCN 的内部排序机制来保证事务的顺序。当 SELECT 语句进入执行阶段时,数据库会确定查询开始执行时记录的 SCN。在图 12-1 中,此 SCN 是 10023。该查询只能看到相对于 SCN 10023 已提交的数据。
在图 12-1 中,SCN 在 10023 之后的块表示已更改的数据,如两个 SCN 为 10024 的块所示。SELECT 语句需要一个与已提交更改一致的块版本。数据库将当前数据块复制到一个新的缓冲区,并应用撤销数据来重建块的先前版本。这些重建的数据块称为读一致性 (CR) 克隆。
在图 12-1 中,数据库创建了两个 CR 克隆:一个块一致到 SCN 10006,另一个块一致到 SCN 10021。数据库返回重建后的数据以供查询。通过这种方式,Oracle AI Database 防止了脏读。
另请参见:"Database Buffer Cache"(数据库缓冲区高速缓存)和 "System Change Numbers (SCNs)"(系统更改号 (SCN))
Read Consistency and Interested Transaction Lists(读一致性与关注事务列表)
每个段块的块头 都包含一个关注事务列表 (ITL)。
数据库使用 ITL 来确定在数据库开始修改块时,某个事务是否尚未提交。
ITL 中的条目描述了哪些事务锁定了行,以及块中哪些行包含已提交和未提交的更改。ITL 指向撤销段中的事务表,该表提供了有关对数据库所做更改的时间信息。
从某种意义上说,块头包含了影响块中每一行的事务的近期历史记录。CREATE TABLE 和 ALTER TABLE 语句的 INITRANS 参数控制保留的事务历史记录量。
另请参见
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》),了解
INITRANS参数
Read Consistency and Deferred Inserts(读一致性与延迟插入)
一种称为延迟插入的特殊插入类型不使用标准的读一致性机制。
延迟插入使用 MEMOPTIMIZE_WRITE 提示来插入到指定为 MEMOPTIMIZE FOR WRITE 的表中。数据库将这些插入缓冲在大型池中,而不是缓冲区高速缓存中。数据库不使用重做和撤销来跟踪这些更改。相反,当空间管理协调器 (SMCO) 将缓冲区写入磁盘时,数据库会自动提交这些更改。这些更改无法回滚。
延迟插入在以下重要方面与传统插入不同:
- 驻留在大型池中的数据(应用程序假定已提交)可能会丢失。例如,数据库实例可能在更改保存到磁盘之前发生故障,即使应用程序报告更改已保存。
- 不允许直接从内存读取数据。在后台进程将更改写入磁盘之前,写入者无法读取自己的更改。在更改写入磁盘之前,任何读取者都无法看到已提交的更改。
必须避免数据丢失的客户端应用程序应在写入大型池后保留数据的本地副本。客户端可以使用 DBMS_MEMOPTIMIZE 程序包来跟踪内存写入的持久性,并使用 DBMS_MEMOPTIMIZE_ADMIN 程序包来强制数据库写入磁盘。
另请参见
- "Large Pool Buffers for Deferred Inserts"(用于延迟插入的大型池缓冲区)
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》),了解有关
MEMOPTIMIZE FOR WRITE提示的更多信息
Locking Mechanisms(锁机制)
通常,多用户数据库使用某种形式的数据锁来解决与数据并发性、一致性和完整性相关的问题。
锁是一种机制,防止访问相同资源的事务之间发生破坏性交互。
另请参见:"Overview of the Oracle AI Database Locking Mechanism"(Oracle AI Database 锁机制概述)
ANSI/ISO Transaction Isolation Levels(ANSI/ISO 事务隔离级别)
已被 ANSI 和 ISO/IEC 采纳的 SQL 标准定义了四个事务隔离级别。这些级别对事务处理吞吐量的影响程度各不相同。
这些隔离级别是根据在并发执行的事务之间必须防止的现象来定义的。这些可防止的现象是:
-
脏读
一个事务读取了另一个尚未提交的事务写入的数据。
-
不可重复读(模糊读)
一个事务重新读取先前读取过的数据,发现另一个已提交的事务已修改或删除了该数据。例如,用户查询一行,随后再次查询同一行,却发现数据已更改。
-
幻读
一个事务重新执行一个返回满足搜索条件的行集的查询,发现另一个已提交的事务插入了满足该条件的额外行。
例如,一个事务查询员工数量。五分钟后,它执行相同的查询,但发现数量增加了 1,因为另一个用户为新员工插入了一条记录。满足查询条件的数据比以前更多,但与模糊读不同的是,先前读取的数据本身并未改变。
SQL 标准根据在特定隔离级别下运行的事务被允许遇到的现象,定义了四个隔离级别。表 12-1 显示了这些级别。
表 12-1 按隔离级别划分的可防止的读现象
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 可能 |
| 可串行化 | 不可能 | 不可能 | 不可能 |
Oracle AI Database 提供读已提交 (默认)和可串行化 隔离级别。此外,数据库还提供只读模式。
另请参见
- "Overview of Oracle AI Database Transaction Isolation Levels"(Oracle AI Database 事务隔离级别概述),了解读已提交、可串行化和只读隔离级别
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》),讨论 Oracle AI Database 对 SQL 标准的符合性
Overview of Oracle AI Database Transaction Isolation Levels(Oracle AI Database 事务隔离级别概述)
事务隔离级别的 ANSI 标准是根据每个隔离级别所允许或阻止的现象来定义的。
Oracle AI Database 提供以下事务隔离级别:
- Read Committed Isolation Level(读已提交隔离级别)
- Serializable Isolation Level(可串行化隔离级别)
- Read-Only Isolation Level(只读隔离级别)
Read Committed Isolation Level(读已提交隔离级别)
在读已提交隔离级别中,事务执行的每个查询只能看到在查询(而非事务)开始之前已提交的数据。
Read Consistency in the Read Committed Isolation Level(读已提交隔离级别中的读一致性)
数据库为每个查询提供一致的结果集,保证数据一致性,无需用户执行任何操作。
Conflicting Writes in Read Committed Transactions(读已提交事务中的冲突写入)
在读已提交事务中,当该事务尝试更改由未提交的并发事务更新的行时,会发生冲突写入。
Serializable Isolation Level(可串行化隔离级别)
在可串行化隔离级别中,事务只能看到在事务(而非查询)开始时已提交的更改,以及该事务自身所做的更改。
Read-Only Isolation Level(只读隔离级别)
只读隔离级别类似于可串行化隔离级别,但只读事务不允许在事务中修改数据,除非用户是 SYS。
另请参见
- 《Oracle AI Database Development Guide》(《Oracle AI Database 开发指南》),了解有关事务隔离级别的更多信息
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》)和《Oracle AI Database PL/SQL Language Reference》(《Oracle AI Database PL/SQL 语言参考》),了解
SET TRANSACTION ISOLATION LEVEL
Read Committed Isolation Level(读已提交隔离级别)
在读已提交隔离级别中,事务执行的每个查询只能看到在查询(而非事务)开始之前已提交的数据。
此隔离级别是默认级别。它适用于很少有事务可能发生冲突的数据库环境。
读已提交事务中的查询会避免读取在查询执行期间提交的数据。例如,如果查询正在扫描一个包含百万行的表,已扫描了一半,此时另一个事务提交了对第 950,000 行的更新,那么该查询在读取第 950,000 行时不会看到此更改。但是,因为数据库不阻止其他事务修改查询已读取的数据,所以其他事务可能会在两次查询执行之间更改数据。因此,两次运行相同查询的事务可能会遇到模糊读和幻读。
Read Consistency in the Read Committed Isolation Level(读已提交隔离级别中的读一致性)
数据库为每个查询提供一致的结果集,保证数据一致性,无需用户执行任何操作。
隐式查询(例如,UPDATE 语句中 WHERE 子句隐含的查询)保证获得一致的结果集。然而,隐式查询中的每条语句都不会看到该 DML 语句本身所做的更改,而是看到更改发生之前存在的数据。
如果 SELECT 列表包含一个 PL/SQL 函数,那么数据库会在该 PL/SQL 函数代码内运行的 SQL 的语句级别上应用语句级读一致性,而不是在父 SQL 级别上。例如,一个函数可以访问一个其数据被其他用户更改并提交的表。对于函数中 SELECT 的每次执行,都会建立一个新的读一致性快照。
另请参见:"Subqueries"(子查询)
Conflicting Writes in Read Committed Transactions(读已提交事务中的冲突写入)
在读已提交事务中,当该事务尝试更改由未提交的并发事务更新的行时,会发生冲突写入。
阻止行修改的事务有时被称为阻塞事务。读已提交事务会等待阻塞事务结束并释放其行锁。
选项如下:
- 如果阻塞事务回滚,那么等待事务将继续更改先前锁定的行,就好像另一个事务从未存在过一样。
- 如果阻塞事务提交并释放其锁,那么等待事务将根据新更改的行继续执行其预期的更新。
下表显示了事务 1(可以是可串行化或读已提交)如何与读已提交事务 2 交互。它展示了一种称为丢失更新的经典情况。即使事务 1 提交了更新,事务 1 所做的更新也不在表中了。制定处理丢失更新的策略是应用程序开发的重要组成部分。
表 12-2 读已提交事务中的冲突写入与丢失更新
| 会话 1 | 会话 2 | 说明 |
|---|---|---|
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6200 Greene 9500 |
无操作。 | 会话 1 查询 Banda、Greene 和 Hintz 的薪水。没有找到名为 Hintz 的员工。 |
SQL> UPDATE employees SET salary = 7000 WHERE last_name = 'Banda'; |
无操作。 | 会话 1 通过更新 Banda 的薪水开始一个事务。事务 1 的默认隔离级别是 READ COMMITTED。 |
| 无操作。 | SQL> SET TRANSACTION ISOLATION LEVEL READ COMMITTED; |
会话 2 开始事务 2,并将隔离级别显式地设置为 READ COMMITTED。 |
| 无操作。 | 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 AI Database 使用读一致性来显示 Banda 在事务 1 所做的未提交更新之前的薪水。 |
| 无操作。 | 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'); |
无操作。 | 事务 1 为员工 Hintz 插入了一行,但没有提交。 |
| 无操作。 | 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 薪水所做的未提交更新,也没有看到事务 1 对 Hintz 的插入。 |
| 无操作。 | SQL> UPDATE employees SET salary = 6300 WHERE last_name = 'Banda'; -- prompt does not return |
事务 2 尝试更新 Banda 行,该行当前被事务 1 锁定,从而导致了冲突写入。事务 2 等待,直到事务 1 结束。 |
SQL> COMMIT; |
无操作。 | 事务 1 提交其工作,结束事务。 |
| 无操作。 | 1 row updated. SQL> |
Banda 行的锁现已释放,因此事务 2 继续执行对 Banda 薪水的更新。 |
| 无操作。 | 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 薪水的更新。 |
| 无操作。 | COMMIT; |
事务 2 提交其工作,结束事务。 |
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6300 Greene 9900 Hintz |
无操作。 | 会话 1 查询 Banda、Greene 和 Hintz 的行。Banda 的薪水是 6300,这是事务 2 所做的更新。事务 1 将 Banda 的薪水更新为 7000 的操作现已"丢失"。 |
另请参见
- "Use of Locks"(锁的使用),了解丢失更新
- "Row Locks (TX)"(行锁 (TX)),了解数据库何时以及为何获取行锁
Serializable Isolation Level(可串行化隔离级别)
在可串行化隔离级别中,事务只能看到在事务(而非查询)开始时已提交的更改,以及该事务自身所做的更改。
可串行化事务的操作环境使其看起来好像没有其他用户在修改数据库中的数据。可串行化隔离适用于以下环境:
- 拥有大型数据库和仅更新少量行的短事务
- 两个并发事务修改相同行的几率相对较低
- 长时间运行的事务主要都是只读的
在可串行化隔离中,通常在语句级别获得的读一致性会扩展到整个事务。事务读取的任何行都保证在重新读取时是相同的。任何查询都保证在事务持续期间返回相同的结果,因此,无论查询运行了多长时间,其他事务所做的更改对查询都是不可见的。可串行化事务不会遇到脏读、模糊读或幻读。
Oracle AI Database 允许可串行化事务修改行,但前提是其他事务所做的更改在该可串行化事务开始时已经提交。当可串行化事务尝试更新或删除在它开始之后提交的不同事务所更改的数据时,数据库会生成一个错误:
ORA-08177: Cannot serialize access for this transaction
当可串行化事务因 ORA-08177 错误而失败时,应用程序可以采取以下几种操作,包括:
- 提交到那时为止已执行的工作
- 执行额外的(但不同的)语句,可能在回滚到事务中更早建立的保存点之后
- 回滚整个事务
下表显示了可串行化事务如何与其他事务交互。如果可串行化事务不尝试更改在它开始之后由其他事务提交的行,则可以避免串行化访问问题。
表 12-3 可串行化事务
| 会话 1 | 会话 2 | 说明 |
|---|---|---|
SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 6200 Greene 9500 |
无操作。 | 会话 1 查询 Banda、Greene 和 Hintz 的薪水。没有找到名为 Hintz 的员工。 |
SQL> UPDATE employees SET salary = 7000 WHERE last_name='Banda'; |
无操作。 | 会话 1 通过更新 Banda 的薪水开始事务 1。默认隔离级别是 READ COMMITTED。 |
| 无操作。 | SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
会话 2 开始事务 2,并将其设置为 SERIALIZABLE 隔离级别。 |
| 无操作。 | 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 AI Database 使用读一致性来显示 Banda 在事务 1 所做的未提交更新之前的薪水。 |
| 无操作。 | 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'); |
无操作。 | 事务 1 为员工 Hintz 插入一行。 |
SQL> COMMIT; |
无操作。 | 事务 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 AI Database 的读一致性确保事务 1 提交的 Hintz 插入和 Banda 更新对事务 2 不可见。事务 2 看到了自己对 Greene 薪水的更新。 |
| 无操作。 | 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'; |
无操作。 | 会话 1 通过更新 Hintz 的薪水开始事务 3。事务 3 的默认隔离级别是 READ COMMITTED。 |
| 无操作。 | SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
会话 2 开始事务 4,并将其设置为 SERIALIZABLE 隔离级别。 |
| 无操作。 | SQL> UPDATE employees SET salary = 7200 WHERE last_name = 'Hintz'; -- prompt does not return |
事务 4 尝试更新 Hintz 的薪水,但被阻塞,因为事务 3 锁定了 Hintz 行。事务 4 在事务 3 之后排队。 |
SQL> COMMIT; |
无操作。 | 事务 3 提交其对 Hintz 薪水的更新,结束事务。 |
| 无操作。 | 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 开始之后提交了 Hintz 更新。 |
| 无操作。 | SQL> ROLLBACK; |
会话 2 回滚事务 4,从而结束该事务。 |
| 无操作。 | SQL> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; |
会话 2 开始事务 5,并将其设置为 SERIALIZABLE 隔离级别。 |
| 无操作。 | SQL> SELECT last_name, salary FROM employees WHERE last_name IN ('Banda', 'Greene', 'Hintz'); LAST_NAME SALARY ------------- ---------- Banda 7000 Greene 9900 Hintz 7100 |
事务 5 查询 Banda、Greene 和 Hintz 的薪水。事务 3 提交的 Hintz 薪水更新可见。 |
| 无操作。 | SQL> UPDATE employees SET salary = 7200 WHERE last_name='Hintz'; 1 row updated. |
事务 5 将 Hintz 的薪水更新为另一个值。因为事务 3 所做的 Hintz 更新是在事务 5 开始之前 提交的,所以避免了串行化访问问题。 注意 :如果另一个事务在事务 5 开始之后更新并提交了 Hintz 行,那么串行化访问问题将再次出现。 |
| 无操作。 | SQL> COMMIT; |
会话 2 提交更新,没有任何问题,结束事务。 |
另请参见
- "Row Locks (TX)"(行锁 (TX))
- "Overview of Transaction Control"(事务控制概述)
Read-Only Isolation Level(只读隔离级别)
只读隔离级别类似于可串行化隔离级别,但只读事务不允许在事务中修改数据,除非用户是 SYS。
只读事务不受 ORA-08177 错误的影响。只读事务对于生成内容必须与事务开始时的时间点保持一致的报告非常有用。
Oracle AI Database 通过根据需要从撤销段重建数据来实现读一致性。由于撤销段以循环方式使用,数据库可能会覆盖撤销数据。长时间运行的报告面临的风险是,读一致性所需的撤销数据可能已被不同事务重用,从而引发快照太旧 错误。适当设置撤销保留期(即数据库在覆盖旧撤销数据之前尝试保留它的最短时间)可以避免此问题。
另请参见
- "Undo Segments"(撤销段)
- 《Oracle AI Database Administrator's Guide》(《Oracle AI Database 管理员指南》),了解如何设置撤销保留期
Overview of the Oracle AI Database Locking Mechanism(Oracle AI Database 锁机制概述)
锁是一种防止破坏性交互的机制。
当事务访问共享数据时,如果交互以错误方式更新数据或错误地更改底层数据结构,则属于破坏性交互。锁在维护数据库并发性和一致性方面起着至关重要的作用。
- Summary of Locking Behavior(锁行为摘要)
数据库维护多种不同类型的锁,具体取决于获取锁的操作。 - Use of Locks(锁的使用)
在单用户数据库中,锁不是必需的,因为只有一个用户在修改信息。但是,当多个用户正在访问和修改数据时,数据库必须提供一种方法来防止对同一数据的并发修改。 - Lock Modes(锁模式)
Oracle AI Database 自动使用最低适用级别的限制性,以提供最高程度的数据并发性,同时又能提供故障安全的数据完整性。 - Lock Conversion and Escalation(锁转换与升级)
Oracle AI Database 必要时会执行锁转换。 - Lock Duration(锁持续时间)
当发生某些事件使得事务不再需要资源时,Oracle AI Database 会自动释放锁。 - Locks and Deadlocks(锁与死锁)
死锁是指两个或多个用户相互等待对方锁定的数据的情况。死锁会阻止某些事务继续工作。
Summary of Locking Behavior(锁行为摘要)
数据库维护多种不同类型的锁,具体取决于获取锁的操作。
通常,数据库使用两种类型的锁:排他锁 和共享锁。在诸如行或表之类的资源上,只能获取一个排他锁,但可以在单个资源上获取多个共享锁。
锁影响读取者和写入者的交互。读取者是对资源的查询,而写入者是修改资源的语句。以下规则总结了 Oracle AI Database 对读取者和写入者的锁行为:
- 行仅在由写入者修改时才被锁定。
当一条语句更新一行时,事务仅获取该行的锁。通过在行级别锁定表数据,数据库最大限度地减少了对同一数据的争用。在正常情况下,数据库不会将行锁升级到块级别或表级别。 - 行的写入者会阻塞同一行的并发写入者。
如果一个事务正在修改一行,那么行锁会阻止不同的事务同时修改同一行。 - 读取者从不阻塞写入者。
因为行的读取者不会锁定该行,所以写入者可以修改该行。唯一的例外是SELECT ... FOR UPDATE语句,这是一种特殊类型的SELECT语句,它会锁定其正在读取的行。 - 写入者从不阻塞读取者。
当写入者正在更改某行时,数据库使用撤销数据为读取者提供该行的一致视图。
注意
在极少数未决分布式事务的特殊情况下,数据读取者可能必须等待相同数据块的写入者。
另请参见
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》),了解
SELECT ... FOR UPDATE - 《Oracle AI Database Administrator's Guide》(《Oracle AI Database 管理员指南》),了解与存疑分布式事务相关的等待
Use of Locks(锁的使用)
在单用户数据库中,锁不是必需的,因为只有一个用户在修改信息。但是,当多个用户正在访问和修改数据时,数据库必须提供一种方法来防止对同一数据的并发修改。
锁实现了以下重要的数据库要求:
- 一致性
会话正在查看或更改的数据,在用户完成操作之前,不得被其他会话更改。 - 完整性
数据和结构必须按正确的顺序反映对其所做的所有更改。
Oracle AI Database 通过其锁机制在事务之间提供数据并发性、一致性和完整性。锁定是自动发生的,无需用户操作。
可以通过对单行的并发更新来说明锁的必要性。在以下示例中,一个简单的基于 Web 的应用程序向最终用户显示员工电子邮件和电话号码。该应用程序使用如下所示的 UPDATE 语句来修改数据:
sql
UPDATE employees
SET email = ?, phone_number = ?
WHERE employee_id = ?
AND email = ?
AND phone_number = ?
在上述 UPDATE 语句中,WHERE 子句中的电子邮件和电话号码值是指定员工的原始、未修改值。此更新确保应用程序修改的行在应用程序上次读取并向用户显示之后未被更改。通过这种方式,应用程序避免了丢失更新 问题,即一个用户覆盖了另一个用户所做的更改,从而实际上丢失了第二个用户的更新(表 12-2 显示了丢失更新的示例)。
表 12-4 显示了两个会话几乎同时尝试修改 employees 表中同一行时的事件顺序。
表 12-4 行锁示例
| T | 会话 1 | 会话 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 记录,并显示 employee_id (118)、email (GHIMURO) 和 phone_number (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 记录,并显示 employee_id (118)、email (GHIMURO) 和 phone_number (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 用户将行中的电话号码更新为 515.555.1234,这获得了 GHIMURO 行的锁。 | |
| 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 AI Database 读一致性确保会话 2 不会看到在 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 AI Database 在执行 SQL 语句时会自动获取必要的锁。例如,在数据库允许会话修改数据之前,会话必须首先锁定数据。锁赋予会话对数据的独占控制权,以便在锁释放之前,没有其他事务可以修改锁定的数据。
因为 Oracle AI Database 的锁机制与事务控制紧密关联,应用程序设计人员只需要正确定义事务,Oracle AI Database 就会自动管理锁定。用户永远不需要显式地锁定任何资源,尽管 Oracle AI Database 也允许用户手动锁定数据。
以下各节解释了对于理解 Oracle AI Database 如何实现数据并发性至关重要的概念。
另请参见
- 《Oracle AI Database PL/SQL Packages and Types Reference》(《Oracle AI Database PL/SQL 包和类型参考》),了解
OWA_OPT_LOCK包,该包包含有助于防止丢失更新的子程序
Lock Modes(锁模式)
Oracle AI Database 自动使用最低适用级别的限制性,以提供最高程度的数据并发性,同时又能提供故障安全的数据完整性。
限制性级别越低,数据可供其他用户访问的可用性就越高。相反,限制性级别越高,其他事务在可以获取的锁类型方面受到的限制就越多。
Oracle AI Database 在多用户数据库中使用两种锁模式:
-
排他锁模式
此模式阻止关联的资源被共享。当事务修改数据时,它会获取排他锁。第一个以排他方式锁定资源的事务是在排他锁释放之前唯一可以更改资源的事务。
-
共享锁模式
此模式允许共享关联的资源,具体取决于所涉及的操作。多个读取数据的用户可以共享数据,每个用户都持有共享锁,以防止需要排他锁的写入者并发访问。多个事务可以获取同一资源上的共享锁。
假设一个事务使用 SELECT ... FOR UPDATE 语句来选择单表行。该事务获取一个排他行锁和一个行共享表锁。行锁允许其他会话修改除被锁定行之外的所有行,而表锁则阻止会话更改表的结构。因此,数据库允许尽可能多的语句执行。
Lock Conversion and Escalation(锁转换与升级)
Oracle AI Database 必要时会执行锁转换。
在锁转换中,数据库会自动将限制性较低的表锁转换为限制性较高的表锁。例如,假设一个事务为某位员工发出 SELECT ... FOR UPDATE,随后又更新了锁定的行。在这种情况下,数据库会自动将行共享表锁转换为行排他表锁。事务对在该事务内插入、更新或删除的所有行持有排他行锁。因为行锁是以最高限制性级别获取的,所以不需要也不执行锁转换。
锁转换不同于锁升级,锁升级发生在大量锁处于一个粒度级别(例如,行)时,数据库将锁提升到更高的粒度级别(例如,表)。如果一个会话锁定了表中的许多行,那么一些数据库会自动将行锁升级为单个表锁。锁的数量减少了,但被锁定内容的限制性增加了。
Oracle AI Database 从不升级锁。 锁升级会大大增加死锁的可能性。假设系统试图代表事务 1 升级锁,但由于事务 2 持有的锁而无法升级。如果事务 2 在继续之前也需要对相同数据进行锁升级,则会产生死锁。
Lock Duration(锁持续时间)
当发生某些事件使得事务不再需要资源时,Oracle AI Database 会自动释放锁。
通常,数据库在事务持续期间持有一个事务内的语句所获取的锁。这些锁防止来自并发事务的破坏性干扰,例如脏读、丢失更新和破坏性 DDL。
注意
由于未索引的外键而在子表上获取的表锁是为语句持续期间持有的,而不是为事务持续期间。此外,
DBMS_LOCK包允许用户自定义锁,这些锁可以随意释放和分配,甚至可以跨越事务边界持有。
Oracle AI Database 在提交或回滚时会释放事务内的语句所获取的所有锁。Oracle AI Database 还会在回滚到保存点时释放该保存点之后获取的锁。但是,只有那些没有等待先前锁定资源的事务才能获取现在可用资源上的锁。等待的事务会继续等待,直到原始事务完全提交或回滚。
另请参见
- "Table 13-3"(表 13-3),其中显示事务等待行为
- "Overview of User-Defined Locks"(用户自定义锁概述),了解有关
DBMS_LOCK的更多信息
Locks and Deadlocks(锁与死锁)
死锁是指两个或多个用户相互等待对方锁定的数据的情况。死锁会阻止某些事务继续工作。
Oracle AI Database 自动检测死锁,并通过回滚死锁中涉及的一条语句来解决死锁,从而释放一组冲突的行锁。数据库向经历语句级回滚的事务返回相应的消息。回滚的语句属于检测到死锁的事务。通常,收到信号的事务应显式回滚,但它可以在等待后重试已回滚的语句。
表 12-5 说明了处于死锁状态的两个事务。
表 12-5 死锁事务
| T | 会话 1 | 会话 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. |
会话 1 提交在 t0 所做的更新,结束事务 1。在 t1 尝试但未成功的更新不会被提交。 | |
| t4 | 1 row updated. SQL> |
事务 2 中在 t1 被事务 1 阻塞的更新被执行。返回提示。 | |
| t5 | SQL> COMMIT; Commit complete. |
会话 2 提交在 t0 和 t1 所做的更新,从而结束事务 2。 |
死锁最常发生在事务显式覆盖 Oracle AI Database 默认锁机制时。因为 Oracle AI Database 不升级锁,也不对查询使用读锁,而是使用行级别(而非页级别)锁定,所以死锁很少发生。
另请参见
- "Overview of Manual Data Locks"(手动数据锁概述)
- 《Oracle AI Database Development Guide》(《Oracle AI Database 开发指南》),了解在显式锁定表时如何处理死锁
Overview of Automatic Locks(自动锁概述)
Oracle AI Database 会自动代表事务锁定资源,以防止其他事务执行需要独占访问同一资源的操作。
数据库会根据资源和正在执行的操作,自动获取不同限制性级别的不同类型的锁。
注意
数据库在执行简单读取时从不锁定行。
Oracle AI Database 的锁分为下表中所示的类别。
表 12-6 锁类别
| 锁 | 描述 | 了解更多 |
|---|---|---|
| DML 锁 | 保护数据。例如,表锁锁定整个表,而行锁锁定选定的行。 | "DML Locks"(DML 锁) |
| DDL 锁 | 保护模式对象的结构------例如,表和视图的数据字典定义。 | "DDL Locks"(DDL 锁) |
| 系统锁 | 保护内部数据库结构,如数据文件。闩锁、互斥锁和内部锁完全是自动的。 | "System Locks"(系统锁) |
DML Locks(DML 锁)
DML 锁 ,也称为数据锁,保证由多个用户并发访问的数据的完整性。
例如,DML 锁可防止两位顾客从在线书商那里购买最后一本可用的图书。DML 锁防止并发冲突的 DML 或 DDL 操作产生破坏性干扰。
DML 语句自动获取以下类型的锁:
- Row Locks (TX)(行锁 (TX))
- Table Locks ™(表锁 ™)
在以下各节中,每种锁或锁模式后面括号中的首字母缩写词是 Oracle Enterprise Manager (Enterprise Manager) 的锁监视器中使用的缩写。Enterprise Manager 对于任何表锁都可能显示 TM,而不是指示表锁的模式(例如 RS 或 SRX)。
-
Row Locks (TX)(行锁 (TX))
行锁 ,也称为 TX 锁 ,是对表中单行的锁。事务会为由INSERT、UPDATE、DELETE、MERGE或SELECT ... FOR UPDATE语句修改的每一行获取一个行锁。该行锁会一直存在,直到事务提交或回滚。 -
Table Locks ™(表锁 ™)
表锁 ,也称为 TM 锁 ,当表被INSERT、UPDATE、DELETE、MERGE、带有FOR UPDATE子句的SELECT或LOCK TABLE语句修改时,由事务获取。 -
Locks and Foreign Keys(锁与外键)
Oracle AI Database 最大限度地提高了与依赖外键相关的父键的并发控制。
Row Locks (TX)(行锁 (TX))
行锁 ,也称为 TX 锁 ,是对表中单行的锁。事务会为由 INSERT、UPDATE、DELETE、MERGE 或 SELECT ... FOR UPDATE 语句修改的每一行获取一个行锁。该行锁会一直存在,直到事务提交或回滚。
行锁主要作为一种排队机制,以防止两个事务修改同一行。数据库始终以排他模式锁定已修改的行,使得其他事务无法修改该行,直到持有锁的事务提交或回滚。行锁定提供了尽可能最细粒度的锁定,从而提供了最佳可能的并发性和吞吐量。
注意
如果事务由于数据库实例故障而终止,那么在整个事务恢复之前,块级恢复会使行变为可用。
如果一个事务获取了某行的锁,那么该事务也会获取包含该行的表的锁。表锁可防止与当前事务中的数据更改相冲突的 DDL 操作。图 12-2 展示了对表中第三行的更新。Oracle AI Database 自动在更新的行上放置一个排他锁,并在表上放置一个子排他锁。
Figure 12-2 Row and Table Locks(图12-2 行锁与表锁)

该图展示了表 EMPLOYEES,包含列 EMPLOYEE_ID, LAST_NAME, EMAIL, HIRE_DATE, JOB_ID, MANAGER_ID, DEPARTMENT_ID。正被更新的行获得排他行锁 (TX)。表获得表锁。已获取排他行锁 (TX) 的行正在被更新。表锁已被获取。
-
Row Locks and Concurrency(行锁与并发性)
此场景说明了 Oracle AI Database 如何使用行锁来实现并发性。
-
Storage of Row Locks(行锁的存储)
与某些使用锁管理器在内存中维护锁列表的数据库不同,Oracle AI Database 将锁信息存储在包含锁定行的数据块中。
Row Locks and Concurrency(行锁与并发性)
此场景说明了 Oracle AI Database 如何使用行锁来实现并发性。
三个会话同时查询相同的行。会话 1 和 2 继续对不同的行进行未提交的更新,而会话 3 不进行任何更新。每个会话看到自己的未提交更新,但看不到任何其他会话的未提交更新。
表 12-7 数据并发性示例
| T | 会话 1 | 会话 2 | 会话 3 | 说明 |
|---|---|---|---|---|
| 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 |
三个不同的会话同时查询员工 100 和 101 的 ID 和薪水。每次查询返回的结果是相同的。 |
| 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.employees 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 修改之前的薪水。 |
另请参见
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》)
- 《Oracle AI Database Reference》(《Oracle AI Database 参考》),了解
V$LOCK
Storage of Row Locks(行锁的存储)
与某些使用锁管理器在内存中维护锁列表的数据库不同,Oracle AI Database 将锁信息存储在包含锁定行的数据块中。
数据库使用排队机制来获取行锁。如果事务需要针对未锁定的行获取锁,则该事务将该锁放入数据块中。此事务修改的每一行都指向存储在块头中的事务 ID 副本。当事务结束时,事务 ID 仍保留在块头中。如果另一个事务要修改某行,它将使用事务 ID 来确定锁是否处于活动状态。如果锁是活动的,则会话请求在锁释放时得到通知。否则,该事务将获取锁。
另请参见
- "Overview of Data Blocks"(数据块概述),了解有关数据块头的更多信息
- 《Oracle AI Database Reference》(《Oracle AI Database 参考》),了解
V$TRANSACTION
Table Locks ™(表锁 ™)
表锁 ,也称为 TM 锁 ,当表被 INSERT、UPDATE、DELETE、MERGE、带有 FOR UPDATE 子句的 SELECT 或 LOCK TABLE 语句修改时,由事务获取。
DML 操作需要表锁来代表事务保留对表的 DML 访问权,并防止可能与事务冲突的 DDL 操作。
表锁可以按以下任一模式持有:
-
行共享 (RS)
此锁,也称为子共享表锁 (SS),表示在表上持有锁的事务已锁定表中的行并打算更新它们。行共享锁是限制性最低的表锁模式,为表提供了最高程度的并发性。
-
行排他表锁 (RX)
此锁,也称为子排他表锁 (SX) ,通常表示持有该锁的事务已更新表行或发出了
SELECT ... FOR UPDATE。SX 锁允许其他事务在同一个表中并发地查询、插入、更新、删除或锁定行。因此,SX 锁允许多个事务同时获取同一个表的 SX 和子共享表锁。 -
共享表锁 (S)
事务持有的共享表锁允许其他事务查询该表(不使用
SELECT ... FOR UPDATE),但仅当单个事务持有共享表锁时才允许更新。因为多个事务可以同时持有共享表锁,所以持有此锁不足以保证事务可以修改该表。 -
共享行排他表锁 (SRX)
此锁,也称为共享子排他表锁 (SSX) ,比共享表锁更具限制性。在给定表上,一次只能有一个事务获取 SSX 锁。事务持有的 SSX 锁允许其他事务查询该表(
SELECT ... FOR UPDATE除外),但不允许更新该表。 -
排他表锁 (X)
此锁是限制性最强的,禁止其他事务执行任何类型的 DML 语句或在表上放置任何类型的锁。
另请参见
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》)
- 《Oracle AI Database Development Guide》(《Oracle AI Database 开发指南》),了解有关表锁的更多信息
Locks and Foreign Keys(锁与外键)
Oracle AI Database 最大限度地提高了与依赖外键相关的父键的并发控制。
锁行为取决于外键列是否已索引。如果外键未索引,那么子表可能会被更频繁地锁定,会发生死锁,并且并发性会降低。因此,几乎总是应该为外键创建索引。唯一的例外是当匹配的唯一键或主键从未被更新或删除时。
-
Locks and Unindexed Foreign Keys(锁与未索引的外键)
当子表的外键列上不存在索引,并且某个会话修改父表中的主键(例如,删除一行或修改主键属性)或将行合并到父表中时,数据库会获取子表上的全表锁。
-
Locks and Indexed Foreign Keys(锁与已索引的外键)
当子表中的外键列已索引,并且某个会话修改父表中的主键(例如,删除一行或修改主键属性)或将行合并到父表中时,数据库不会获取子表上的全表锁。
Locks and Unindexed Foreign Keys(锁与未索引的外键)
当子表的外键列上不存在索引,并且某个会话修改父表中的主键(例如,删除一行或修改主键属性)或将行合并到父表中时,数据库会获取子表上的全表锁。
当以下两个条件都成立时,数据库会获取子表上的全表锁:
- 子表的外键列上不存在索引。
- 某个会话修改父表中的主键(例如,删除一行或修改主键属性)或将行合并到父表中。
注意
对父表的插入不会获取阻止对子表执行 DML 的阻塞表锁。在插入的情况下,数据库会在子表上获取一个锁,该锁可防止结构更改,但不能阻止对现有行或新添加行的修改。
假设 hr.departments 表是 hr.employees 的父表,而 hr.employees 包含未索引的外键 employees.department_id。下图展示了一个会话正在修改 departments 表中部门 60 的主键属性。
Figure 12-3 Locking Mechanisms with Unindexed Foreign Key(图12-3 未索引外键的锁机制)

该图左侧为 "Dependent or Child Table EMPLOYEES"(依赖表或子表 EMPLOYEES),右侧为 "Referenced or Parent Table DEPARTMENTS"(被引用表或父表 DEPARTMENTS)。图中标示了 "Unindexed Foreign Key"(未索引的外键)和 "Parent Key Primary key of referenced table"(父键 被引用表的主键)。在 EMPLOYEES 表上标示了 "Full table lock acquired"(已获取全表锁),在 DEPARTMENTS 表上标示了 "Exclusive row lock (TX) acquired Primary key modified"(已获取排他行锁 (TX) 主键已修改)。下方 Index Leaf Block(索引叶块)中显示了一些条目。
在图 12-3 中,在修改部门 60 的主键期间,数据库获取了 employees 上的全表锁。此锁允许其他会话查询,但不能更新 employees 表。例如,会话不能更新员工电话号码。employees 上的表锁在 departments 表上的主键修改完成后立即释放。如果 departments 中有多行经历主键修改,则对于 departments 中修改的每一行,都会获取并释放一次 employees 上的表锁。
注意
对子表的 DML 不会获取父表上的表锁。
Locks and Indexed Foreign Keys(锁与已索引的外键)
当子表中的外键列已索引,并且某个会话修改父表中的主键(例如,删除一行或修改主键属性)或将行合并到父表中时,数据库不会获取子表上的全表锁。
父表上的锁会阻止事务获取排他表锁,但不会阻止在主键修改期间对父表或子表执行 DML。如果父表上发生主键修改的同时子表上发生更新,则这种情况更可取。
图 12-4 展示了子表 employees 具有已索引的 department_id 列。一个事务从 departments 中删除了部门 280。此删除操作不会 导致数据库在 employees 表上获取全表锁,这与在 "Locks and Unindexed Foreign Keys"(锁与未索引的外键)中描述的场景不同。
Figure 12-4 Locking Mechanisms with Indexed Foreign Key(图12-4 已索引外键的锁机制)

该图与图12-3结构类似,但标示了 "Indexed Foreign Key"(已索引的外键)。在 EMPLOYEES 表上标示了 "No table lock acquired"(未获取表锁),在 DEPARTMENTS 表上标示了 "Exclusive row lock (TX) acquired Row being deleted"(已获取排他行锁 (TX) 正被删除的行)。下方 Index Leaf Block(索引叶块)中包含了 280,rowid 条目。
如果子表指定了 ON DELETE CASCADE,那么从父表删除可能会导致从子表删除。例如,删除部门 280 可能会导致从 employees 表中删除属于被删除部门的员工的记录。在这种情况下,等待和锁规则与从父表删除行之后再从子表删除行相同。
另请参见
- "Foreign Key Constraints"(外键约束)
- "Introduction to Indexes"(索引简介)
DDL Locks(DDL 锁)
数据字典 (DDL) 锁在正在进行的 DDL 操作作用于或引用某个模式对象时,保护该对象的定义。
在 DDL 操作期间,仅被修改或引用的单个模式对象会被锁定。数据库从不锁定整个数据字典。
Oracle AI Database 会自动代表需要 DDL 锁的任何 DDL 事务获取 DDL 锁。用户不能显式请求 DDL 锁。例如,如果用户创建一个存储过程,那么 Oracle AI Database 会自动为过程定义中引用的所有模式对象获取 DDL 锁。这些 DDL 锁可防止这些对象在过程编译完成之前被更改或删除。
-
Exclusive DDL Locks(排他 DDL 锁)
排他 DDL 锁阻止其他会话获取 DDL 或 DML 锁。
-
Share DDL Locks(共享 DDL 锁)
资源的共享 DDL 锁可防止与冲突的 DDL 操作产生破坏性干扰,但允许类似 DDL 操作的数据并发性。
-
Breakable Parse Locks(可中断的解析锁)
SQL 语句或 PL/SQL 程序单元为其引用的每个模式对象都持有一个解析锁。
Exclusive DDL Locks(排他 DDL 锁)
排他 DDL 锁阻止其他会话获取 DDL 或 DML 锁。
大多数 DDL 操作都需要资源的排他 DDL 锁,以防止与可能修改或引用同一模式对象的其他 DDL 操作产生破坏性干扰。例如,DROP TABLE 不允许在 ALTER TABLE 正在向表中添加列时删除该表,反之亦然。
排他 DDL 锁持续 DDL 语句执行和自动提交的整个期间。在获取排他 DDL 锁期间,如果另一个操作在模式对象上持有另一个 DDL 锁,则获取操作会等待,直到旧的 DDL 锁被释放,然后再继续。
另请参见
- "Share DDL Locks"(共享 DDL 锁),描述了不需要排他锁来防止破坏性干扰的情况
Share DDL Locks(共享 DDL 锁)
资源的共享 DDL 锁可防止与冲突的 DDL 操作产生破坏性干扰,但允许类似 DDL 操作的数据并发性。
例如,当运行 CREATE PROCEDURE 语句时,包含该语句的事务会为所有引用的表获取共享 DDL 锁。其他事务可以并发地创建引用相同表的过程,并获取相同表上的并发共享 DDL 锁,但任何事务都不能在任何引用的表上获取排他 DDL 锁。
共享 DDL 锁持续 DDL 语句执行和自动提交的整个期间。因此,持有共享 DDL 锁的事务保证所引用的模式对象的定义在事务期间保持不变。
Breakable Parse Locks(可中断的解析锁)
SQL 语句或 PL/SQL 程序单元为其引用的每个模式对象都持有一个解析锁。
获取解析锁是为了在引用的对象被更改或删除时,关联的共享 SQL 区域可以被无效化。解析锁之所以被称为可中断的解析锁,是因为它不禁止任何 DDL 操作,并且可以被中断以允许冲突的 DDL 操作。
解析锁在 SQL 语句执行的解析阶段在共享池中获取。只要该语句的共享 SQL 区域保留在共享池中,该锁就一直持有。
另请参见:"Shared Pool"(共享池)
System Locks(系统锁)
Oracle AI Database 使用各种类型的系统锁来保护内部数据库和内存结构。这些机制对用户不可访问,因为用户无法控制它们的发生或持续时间。
-
Latches(闩锁)
闩锁是一种简单、低级别的序列化机制,用于协调对共享数据结构、对象和文件的多用户访问。 -
Mutexes(互斥锁)
互斥对象 (mutex) 是一种低级机制,可防止内存中的对象在被并发进程访问时老化失效或被损坏。互斥锁类似于闩锁,但闩锁通常保护一组对象,而互斥锁保护单个对象。 -
Internal Locks(内部锁)
内部锁是比闩锁和互斥锁更高级别、更复杂的机制,服务于各种目的。
Latches(闩锁)
闩锁是一种简单、低级别的序列化机制,用于协调对共享数据结构、对象和文件的多用户访问。
闩锁保护共享内存资源在多个进程访问时免遭损坏。具体来说,闩锁保护数据结构免受以下情况的影响:
- 多个会话的并发修改
- 在一个会话修改时被另一个会话读取
- 在访问期间被解除分配(老化失效)
通常,单个闩锁保护 SGA 中的多个对象。例如,后台进程(如 DBW 和 LGWR)从共享池 分配内存以创建数据结构。为了分配此内存,这些进程使用一个共享池闩锁,该闩锁串行化访问,以防止两个进程同时尝试检查或修改共享池。在分配了内存之后,其他进程可能需要访问共享池区域,例如解析所需的库缓存。在这种情况下,进程仅闩锁库缓存,而不是整个共享池。
与行锁等排队闩锁不同,闩锁不允许会话排队。当闩锁变为可用时,第一个请求闩锁的会话将获得对它的独占访问权。闩锁自旋 现象是指进程在循环中反复请求闩锁,而闩锁休眠是指进程在重新请求闩锁之前释放 CPU。
通常,Oracle 进程在操作或查看数据结构时,会在极短的时间内获取闩锁。例如,在处理单个员工的薪水更新时,数据库可能会获取并释放数千个闩锁。闩锁的实现依赖于操作系统,特别是在进程是否等待闩锁以及等待多长时间方面。
闩锁的增加意味着并发性的降低。例如,过多的硬解析操作会产生对库缓存闩锁的争用。V$LATCH 视图包含每个闩锁的详细使用统计信息,包括每个闩锁被请求和等待的次数。
另请参见
- "SQL Parsing"(SQL 解析)
- 《Oracle AI Database Reference》(《Oracle AI Database 参考》),了解
V$LATCH - 《Oracle AI Database Performance Tuning Guide》(《Oracle AI Database 性能调整指南》),了解等待事件统计信息
Mutexes(互斥锁)
互斥对象 (mutex) 是一种低级机制,可防止内存中的对象在被并发进程访问时老化失效或被损坏。互斥锁类似于闩锁,但闩锁通常保护一组对象,而互斥锁保护单个对象。
互斥锁提供以下几个优点:
- 互斥锁可以减少争用的可能性。
因为闩锁保护多个对象,当进程尝试并发访问这些对象中的任何一个时,它可能成为瓶颈。通过串行化对单个对象而非一组对象的访问,互斥锁提高了可用性。 - 互斥锁占用的内存比闩锁少。
- 在共享模式下,互斥锁允许多个会话并发引用。
Internal Locks(内部锁)
内部锁是比闩锁和互斥锁更高级别、更复杂的机制,服务于各种目的。
数据库使用以下类型的内部锁:
-
字典缓存锁
这些锁持续时间非常短,在字典缓存中的条目被修改或使用时持有。它们保证正在解析的语句不会看到不一致的对象定义。字典缓存锁可以是共享的或排他的。共享锁在解析完成时释放,而排他锁在 DDL 操作完成时释放。
-
文件和日志管理锁
这些锁保护各种文件。例如,内部锁保护控制文件,以便一次只有一个进程可以更改它。另一个锁协调联机重做日志文件的使用和归档。数据文件被锁定,以确保多个实例以共享模式装载数据库,或者一个实例以排他模式装载数据库。因为文件和日志锁指示文件的状态,所以这些锁必然要持有很长时间。
-
表空间和撤销段锁
这些锁保护表空间和撤销段。例如,访问数据库的所有实例必须就表空间是联机还是脱机达成一致。撤销段被锁定,以便只有一个数据库实例可以写入某个段。
另请参见:"Data Dictionary Cache"(数据字典缓存)
Overview of Manual Data Locks(手动数据锁概述)
您可以手动覆盖 Oracle AI Database 的默认锁机制。
Oracle AI Database 自动执行锁定以确保数据并发性、数据完整性和语句级读一致性。但是,在以下情况下,覆盖默认锁机制很有用:
-
应用程序需要事务级读一致性或可重复读。
在这种情况下,查询必须在事务持续期间产生一致的数据,而不反映其他事务的更改。您可以通过使用显式锁定、只读事务、可串行化事务或覆盖默认锁来实现事务级读一致性。
-
应用程序需要事务对资源的独占访问权,以便该事务不必等待其他事务完成。
您可以在会话级别或事务级别覆盖 Oracle AI Database 的默认锁机制。在会话级别,会话可以使用 ALTER SESSION 语句设置所需的事务隔离级别。在事务级别,包含以下 SQL 语句的事务会覆盖 Oracle AI Database 的默认锁:
SET TRANSACTION ISOLATION LEVEL语句LOCK TABLE语句(它锁定一个表,或者在与视图一起使用时锁定基表)SELECT ... FOR UPDATE语句
由上述语句获取的锁在事务结束后或回滚到释放它们的保存点后被释放。
如果在任何级别覆盖了 Oracle AI Database 的默认锁机制,那么数据库管理员或应用程序开发人员应确保覆盖锁定的过程操作正确。锁定过程必须满足以下标准:保证数据完整性,数据并发性可接受,并且不可能发生死锁或对死锁进行了适当处理。
另请参见
- 《Oracle AI Database SQL Language Reference》(《Oracle AI Database SQL 语言参考》),了解
LOCK TABLE和SELECT的描述 - 《Oracle AI Database Development Guide》(《Oracle AI Database 开发指南》),了解如何手动锁定表
Overview of User-Defined Locks(用户自定义锁概述)
借助 Oracle AI Database 锁管理服务,您可以为特定应用程序定义自己的锁。
例如,您可以创建一个锁来串行化对文件系统上消息日志的访问。由于保留的用户锁与 Oracle AI Database 锁相同,因此它具有所有 Oracle AI Database 锁功能,包括死锁检测。用户锁永远不会与 Oracle AI Database 锁冲突,因为它们以前缀 UL 标识。
Oracle AI Database 锁管理服务可通过 DBMS_LOCK 程序包中的过程使用。您可以在 PL/SQL 块中包含以下功能的语句:
- 请求特定类型的锁
- 为该锁指定一个在相同或另一个实例中的另一个过程中可识别的唯一名称
- 更改锁类型
- 释放锁
另请参见
- 《Oracle AI Database Development Guide》(《Oracle AI Database 开发指南》),了解有关 Oracle AI Database 锁管理服务的更多信息
- 《Oracle AI Database PL/SQL Packages and Types Reference》(《Oracle AI Database PL/SQL 包和类型参考》),了解有关
DBMS_LOCK的信息