【发布时间】:2011-06-03 16:18:25
【问题描述】:
说明
我有一个 Oracle 存储过程,它在本地开发实例和多个客户端测试和生产实例上运行了 7 年左右,运行 Oracle 8、9、10 和最近 11。它一直运行到升级到 Oracle 11g。基本上,该过程打开一个引用游标,更新一个表然后完成。在 10g 中,光标将包含预期的结果,但在 11g 中,光标将为空。升级到 11g 后没有更改 DML 或 DDL。这种行为在我尝试过的每个 10g 或 11g 实例上都是一致的(10.2.0.3、10.2.0.4、11.1.0.7、11.2.0.1 - 都在 Windows 上运行)。
具体的代码要复杂得多,但可以用一些现实的概述来解释这个问题:我在一个标题表中有一些数据和一堆将输出为 PDF 的子表。标题表有一个布尔值(NUMBER(1),其中 0 为假,1 为真)列,指示该数据是否已被处理。
视图仅限于显示未处理的行(视图还连接其他一些表,进行一些内联查询和函数调用等)。因此,在打开游标时,视图显示一行或多行,然后在打开游标后运行更新语句以翻转头表中的标志,发出提交,然后过程完成。
在 10g 上,游标打开,它包含行,然后更新语句翻转标志并且第二次运行该过程将不会产生任何数据。
在 11g 上,游标 从不 包含行,就好像游标直到更新语句运行后才打开。
我担心 11g 中可能发生了一些变化(希望是可以配置的设置),这可能会影响其他程序和其他应用程序。我想知道的是是否有人知道为什么两个数据库版本之间的行为不同,以及是否可以在不更改代码的情况下解决问题。
更新 1: 我设法将问题追溯到一个独特的约束条件。似乎当 11g 中存在唯一约束时,无论我是针对实际对象运行真实世界代码还是以下简单示例,问题都是 100% 可重现的。
更新 2: 我能够完全消除方程中的视图。我更新了简单示例,以显示即使直接针对表查询时也存在问题。
简单示例
CREATE TABLE tbl1
(
col1 VARCHAR2(10),
col2 NUMBER(1)
);
INSERT INTO tbl1 (col1, col2) VALUES ('TEST1', 0);
/* View is no longer required to demonstrate the problem
CREATE OR REPLACE VIEW vw1 (col1, col2)
AS
SELECT col1, col2
FROM tbl1
WHERE col2 = 0;
*/
CREATE OR REPLACE PACKAGE pkg1
AS
TYPE refWEB_CURSOR IS REF CURSOR;
PROCEDURE proc1 (crs OUT refWEB_CURSOR);
END pkg1;
CREATE OR REPLACE PACKAGE BODY pkg1
IS
PROCEDURE proc1 (crs OUT refWEB_CURSOR)
IS
BEGIN
OPEN crs FOR
SELECT col1
FROM tbl1
WHERE col1 = 'TEST1'
AND col2 = 0;
UPDATE tbl1
SET col2 = 1
WHERE col1 = 'TEST1';
COMMIT;
END proc1;
END pkg1;
匿名区块演示
DECLARE
crs1 pkg1.refWEB_CURSOR;
TYPE rectype1 IS RECORD (
col1 vw1.col1%TYPE
);
rec1 rectype1;
BEGIN
pkg1.proc1 ( crs1 );
DBMS_OUTPUT.PUT_LINE('begin first test');
LOOP
FETCH crs1
INTO rec1;
EXIT WHEN crs1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(rec1.col1);
END LOOP;
DBMS_OUTPUT.PUT_LINE('end first test');
END;
/* After creating this index, the problem is seen */
CREATE UNIQUE INDEX unique_col1 ON tbl1 (col1);
/* Reset data to initial values */
TRUNCATE TABLE tbl1;
INSERT INTO tbl1 (col1, col2) VALUES ('TEST1', 0);
DECLARE
crs1 pkg1.refWEB_CURSOR;
TYPE rectype1 IS RECORD (
col1 vw1.col1%TYPE
);
rec1 rectype1;
BEGIN
pkg1.proc1 ( crs1 );
DBMS_OUTPUT.PUT_LINE('begin second test');
LOOP
FETCH crs1
INTO rec1;
EXIT WHEN crs1%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(rec1.col1);
END LOOP;
DBMS_OUTPUT.PUT_LINE('end second test');
END;
10g 上的输出示例:
开始第一次测试
测试1
结束第一次测试
开始第二次测试
测试1
结束第二次测试
11g 上的输出示例:
开始第一次测试
测试1
结束第一次测试
开始第二次测试
结束第二次测试
澄清
我无法删除 COMMIT,因为在实际场景中,该过程是从 Web 应用程序调用的。当前端的数据提供者调用该过程时,它无论如何都会在与数据库断开连接时发出一个隐式 COMMIT。因此,如果我在过程中删除 COMMIT,那么是的,匿名块演示会起作用,但现实世界的场景不会,因为 COMMIT 仍然会发生。
问题
为什么 11g 的行为不同?除了重写代码我还能做什么?
【问题讨论】:
-
感谢您提供代码,但由于您显示的代码完美运行,因此效果不佳。我们需要一个与您遇到的相同方式失败的示例。我通常会在制作示例的过程中找出问题所在,尤其是在具有触发器、约束、基于函数的索引等的复杂应用程序中。另外,您在上面的代码中存在多用户并发问题,所以您'在这方面也不完全安全。
-
我复制了所有引用的对象,并开始一次一个地删除它们,直到我最终(几个小时后!)找到了罪魁祸首:一个独特的约束!我已经用新信息更新了问题,现在该示例应该清楚地展示了我遇到的行为。
-
在您的实际情况中,视图是否也只看到索引中的列?唯一让我印象深刻的是它不需要在第二种情况下访问表数据 - 但看不出它为什么重要。还想知道如果省略包中的提交是否会发生同样的事情(没有要测试的 11g 实例!)。
-
@Alex Poole:在现实世界的示例中,视图要复杂得多,许多列来自许多表。您的评论帮助指导我在没有视图的情况下尝试它,并且我能够验证该视图与问题无关。谢谢!我更新了简单示例,通过在直接查询表时显示问题来使其更简单。 :)
标签: oracle stored-procedures plsql oracle10g oracle11g