【问题标题】:Oracle SQL- Lock rows with INSERT INTOOracle SQL- 使用 INSERT INTO 锁定行
【发布时间】:2014-01-20 16:05:12
【问题描述】:

我在 Oracle SQL 中遇到了以下问题。 gt_general2 是一个全局临时表。

INSERT INTO gt_general2 (n_1, n_2, n_3)
select p.x_id, a.y_id, a.dollar_amt
from table_P p, table_A a
where p.x_id = a.x_id
  and a.h_date = i_date
  and a.dollar_amt > 0
for update of p.x_id, a.dollar_amt nowait; --this is the problem statement

Oracle 不喜欢我尝试将 INSERT...SELECT 语法与 SELECT...FOR 语法一起使用。 我想在执行此插入时锁定行,因为真正的查询实际上相当复杂(并且对其他视图进行一些检查以确保父记录存在等),这样我可以在执行时插入临时表一次通过行锁定。

还有其他一些我缺少的语法可以让我这样做吗?在概念层面上,我看不出 Oracle 不允许我从表 P 和 A 中选择行、将它们锁定在进程中并将值插入另一个表 Z 的任何原因。毕竟我已经可以做到这一点:

select p.x_id, a.y_id
BULK COLLECT into x_id_list, y_id_list
from table_P, table_A
where ...
for update nowait;

当然可以通过执行此查询两次来锁定行——一次锁定,一次插入。以前,我这样做并使用锁定查询来检索数组中的“x_id”值以帮助执行 INSERT INTO 查询。但我遇到的问题是 x_id 值不足以正确识别我想要的行;我需要这对 (x_id, y_id) 并且无法创建新的嵌套表类型来存储它。

【问题讨论】:

  • “无法创建新的嵌套表类型来存储它”……因为技术限制或特权?

标签: sql oracle plsql locking


【解决方案1】:

您不能将INSERT 与SELECT FOR UPDATE 组合在一个语句中。但是,您可以使用常规 SELECT FOR UPDATE 并使用批量收集和批量插入来实现几乎相似的性能。

如果您在 PL/SQL 代码中声明集合,您也不需要创建新的永久 SQL 类型对象。

这是一个例子,首先是设置:

SQL> CREATE TABLE table_a (x_id NUMBER, y_id NUMBER, dollar_amt NUMBER);
Table created
SQL> CREATE TABLE table_p (x_id NUMBER);
Table created
SQL> INSERT INTO table_a VALUES (1, 1, 0);
1 row inserted
SQL> INSERT INTO table_a VALUES (2, 1, 10);
1 row inserted
SQL> INSERT INTO table_a VALUES (3, 1, 20);
1 row inserted
SQL> INSERT INTO table_p VALUES (2);
1 row inserted
SQL> INSERT INTO table_p VALUES (3);
1 row inserted
SQL> INSERT INTO table_p VALUES (4);
1 row inserted
SQL> CREATE GLOBAL TEMPORARY TABLE gt_general2(
  2     n_1 NUMBER, 
  3     n_2 NUMBER, 
  4     n_3 NUMBER);
Table created

锁定和插入过程(在 11.2.0.2.0 上测试):

SQL> DECLARE
  2     CURSOR cc IS
  3        SELECT p.x_id, a.y_id, a.dollar_amt
  4          FROM table_P p, table_A a
  5         WHERE p.x_id = a.x_id
  6           --AND a.h_date = i_date
  7           AND a.dollar_amt > 0
  8           FOR UPDATE OF p.x_id, a.dollar_amt NOWAIT;
  9     TYPE table_rec IS TABLE OF cc%ROWTYPE;
 10     l_table table_rec;
 11  BEGIN
 12     OPEN cc;
 13     LOOP
 14        -- bulk fetch
 15        FETCH cc BULK COLLECT INTO l_table LIMIT 100;
 16        EXIT WHEN l_table.count=0;
 17        -- bulk insert
 18        FORALL i IN 1..l_table.count
 19           INSERT INTO gt_general2 (n_1, n_2, n_3)
 20              VALUES (l_table(i).x_id,
 21                      l_table(i).y_id,
 22                      l_table(i).dollar_amt);
 23     END LOOP;
 24     CLOSE cc;
 25  END;
 26  /
PL/SQL procedure successfully completed

这将在一次通过中批量执行 100 行的批量选择/插入。这应该比 lock 语句后跟 insert 语句更快。另外,您可以保证插入的行与锁定的行完全相同(而如果表已被修改,则两个语句一个接一个地执行可能会返回不一致的结果)。

【讨论】:

  • 您好,谢谢您的建议。不过,我遇到了这个问题。它编译得很好,但是当我运行它时发生了崩溃:从第 7 行开始的错误命令 - 错误报告 - 没有更多数据可以从套接字读取。 这意味着这导致了某种未处理的异常我'我猜,因为我在用这个概念创建的小测试程序中有一个异常块。
  • 不断得到错误。这是由将游标批量收集到 NT 中的代码引起的,因为在注释掉所有后续位之后,该部分仍然会导致错误。实际上即使没有批量收集,这仍然失败:x_count := 0; x_lock_updts.EXTEND; --锁定更新行;打开 cx_lock;循环 x_count := x_count+1;获取 cx_lock 到 x_lock_updts(x_count);当 cx_lock%NOTFOUND 时退出; x_lock_updts.EXTEND;结束循环;关闭 cx_lock;
  • 您的设置有其他问题,我提供了一个完整的示例。你用的是什么版本?如果您使用的是 10g 或更早的版本,您可能希望将记录表拆分为 3 个嵌套表并批量提取到它们中。
  • 它是 11G R2。我和我们的 DBA 谈过,他说我们的 Oracle 版本中有一些错误,可能就是其中之一,他还说升级到下一个版本很痛苦,所以我想我必须解决它与这个。这是非常有用的信息,因为我一直在使用集合来尝试加快速度,但我不知道您可以在光标行类型上定义 NT。
【解决方案2】:

这真的取决于你为什么要这样做。

如果您需要访问事务开始时的行中的值,而不是每个查询开始时的值,无论它们是否更改,那么我会考虑更改会话以更改将读一致性模式改为“可序列化”,而不是默认的“已提交读”。

如果您真的要回去更新这些行,那么我会考虑使用公用表表达式 (CTE) 而不是全局临时表,将所有逻辑封装在一个查询中。在许多情况下,GTT 和 CTE 的优化几乎相同,当然您不能在流程的中途对 CTE 运行分析,或针对它创建索引。

【讨论】:

  • 这是第二种情况 - 过程是 1) 通过查询找到核心表中的行并将它们锁定以进行更新,2) 将一堆从这些行派生的值插入到临时表中, 3) 通过游标在临时表中执行大量美元金额之间的重新分配计算,4) 使用临时表还存储父序列 NEXTVAL 值以供子记录访问并插入到单独的事务日志表中,5 ) 返回并使用新值更新核心表。我尝试使用 WITH () 表,但过程太复杂,无法使用。
猜你喜欢
  • 2011-02-08
  • 2011-01-22
  • 1970-01-01
  • 2019-01-10
  • 1970-01-01
  • 2021-11-19
  • 2014-04-23
  • 2017-02-07
  • 2014-11-27
相关资源
最近更新 更多