存在唯一索引时,Oracle 10g 和 11g 之间 REF CURSOR 的行为不同?

Posted

技术标签:

【中文标题】存在唯一索引时,Oracle 10g 和 11g 之间 REF CURSOR 的行为不同?【英文标题】:Different behavior for REF CURSOR between Oracle 10g and 11g when unique index present? 【发布时间】: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:在现实世界的示例中,视图要复杂得多,许多列来自许多表。您的评论帮助指导我在没有视图的情况下尝试它,并且我能够验证该视图与问题无关。谢谢!我更新了简单示例,通过在直接查询表时显示问题来使其更简单。 :) 【参考方案1】:

这似乎是最近才发现的一个错误。 Metalink Bug 1045196 描述了确切的问题。希望尽快发布补丁。对于那些无法通过 Metalink 墙的人,这里有一些细节:

元链接

错误 10425196:PL/SQL 返回 REF 游标在 11.1.0.6 和 10.2.0.5 上的作用不同

类型:缺陷 严重性:2 - 服务严重中断 状态:代码错误 创建时间:2010 年 12 月 22 日

原始病例提交的诊断分析: - 10.2.0.4 Windows 预期行为 - 10.2.0.5 Solaris 预期行为 - 11.1.0.6 Solaris 意外行为 - 11.1.0.7 Windows 意外行为 - 11.2.0.1 Solaris 意外行为 - 11.2.0.2 Solaris 意外行为

我可以确认的更多细节: - 10.2.0.3 Windows 预期行为 - 11.2.0.1 Windows 意外行为

其他详情

更改 OPTIMIZER_FEATURES_ENABLE='10.2.0.4' 参数并不能解决问题。所以它似乎更多地与 11g 数据库引擎中的设计更改有关,而不是优化器调整。

代码解决方法

这似乎是查询表时使用索引的结果,而不是更新表和/或提交的行为。使用我上面的示例,这里有两种方法可以确保查询不使用索引。两者都可能影响查询的性能。

在发布补丁之前可能暂时可以接受影响查询的性能,但我相信按照@Edgar Chupit 的建议使用 FLASHBACK 可能会影响整个实例的性能(或者在某些实例上可能不可用),因此该选项有些人可能无法接受。无论哪种方式,此时代码更改似乎是唯一已知的解决方法。

方法 1:更改代码以将列包装在函数中,以防止使用这一列上的唯一索引。就我而言,这是可以接受的,因为尽管该列是唯一的,但它永远不会包含小写字符。

    SELECT col1
      FROM tbl1
     WHERE UPPER(col1) = 'TEST1'
       AND col2 = 0;

方法 2:更改您的查询以使用阻止索引被使用的提示。您可能希望 NO_INDEX(unique_col1) 提示起作用,但事实并非如此。 RULE 提示不起作用。您可以使用 FULL(tbl1) 提示,但与使用 method 1 相比,这可能会降低您的查询速度。

    SELECT /*+ FULL(tbl1) */ col1
      FROM tbl1
     WHERE col1 = 'TEST1'
       AND col2 = 0;

Oracle 的回应和建议的解决方法

Oracle 支持部门终于响应了以下 Metalink 更新:

Oracle 支持 - 格林威治标准时间 2011 年 7 月 20 日上午 5:51:19 [ODM 建议的解决方案] 开发人员报告说,这将是一个需要解决和解决的重大问题 建议应用以下解决方法: 使用以下未记录的参数编辑 init.ora/spfile: “_row_cr” = 假 Oracle 支持 - 2011 年 7 月 20 日上午 5:49:20 GMT-07:00 [ODM 原因说明] 开发已确定这是一个缺陷 Oracle 支持 - 2011 年 7 月 20 日上午 5:48:27 GMT-07:00 [ODM 原因确定] 原因已追溯到行源游标优化 Oracle 支持 - 2011 年 7 月 20 日上午 5:47:27 GMT-07:00 [ODM 问题验证] 开发已确认这是 11.2.0.1 中的问题

在进一步的通信之后,听起来好像这并没有被视为一个错误,而是一个向前推进的设计决策:

Oracle 支持 - 格林威治标准时间 2011 年 7 月 21 日上午 5:58:07 [ODM 提出的解决方案理由] 从 10.2.0.5 开始(包括 11.2.0.2),我们有一个优化称为 ROW CR 仅适用于使用唯一索引的查询 确定表中的行。 这种优化的简要概述是,我们尽量避免回滚,而 如果当前块没有未提交的更改,则构造一个 CR 块。 所以在 11.2.0.2 中看到的差异是因为这种优化。这 建议的解决方法是关闭此优化,以便事情会 完全按照他们在 10.2.0.4 中的工作方式工作

在我们的案例中,鉴于我们的客户端环境,并且由于它与单个存储过程隔离,我们将继续使用我们的代码解决方法来防止任何未知的实例范围的副作用影响其他应用程序和用户。

【讨论】:

【参考方案2】:

这确实是个奇怪的问题,谢谢分享!

从 Oracle 11.1 开始,这看起来确实像是 Oracle 中的行为变化,甚至在 metalink 上也确认了具有类似问题的错误 (bug#10425196)。不幸的是,目前关于主题的金属链接没有太多可用的信息,但我也打开了 Oracle 的 SR,要求提供更多信息。

虽然目前我无法解释为什么会发生这种情况,以及是否有(隐藏的)参数可以将此行为反转回 10g 样式,但我想我可以为您提供解决方法。您可以使用 Oracle 闪回查询功能强制 Oracle 在预期的时间点检索数据。

如果您将代码更改如下:

OPEN crs FOR 
  SELECT col1
>>> FROM vw1 as of scn dbms_flashback.get_system_change_number
   WHERE col1 = 'TEST1';

那么结果应该和10g一样。

这是原始测试用例的简化版本:

SQL> drop table tbl1;
Table dropped
SQL> create table tbl1(col1 varchar2(10), col2 number);
Table created
SQL> create unique index tbl1_idx on tbl1(col1);
Index created
SQL> insert into tbl1(col1,col2) values('TEST1',0);
1 row inserted
SQL> DECLARE
  2    cursor web_cursor is
  3          SELECT col1
  4            FROM tbl1
  5           WHERE col2 = 0 and col1 = 'TEST1';
  6  
  7    rec1  web_cursor%rowtype;
  8  BEGIN
  9    OPEN web_cursor;
 10  
 11    UPDATE tbl1
 12       SET col2 = 1
 13     WHERE col1 = 'TEST1';
 14  
 15    -- different result depending on commit!
 16    commit;
 17  
 18     DBMS_OUTPUT.PUT_LINE('Start');
 19     LOOP
 20        FETCH web_cursor
 21         INTO rec1;
 22  
 23        EXIT WHEN web_cursor%NOTFOUND;
 24  
 25        DBMS_OUTPUT.PUT_LINE(rec1.col1);
 26     END LOOP;
 27     DBMS_OUTPUT.PUT_LINE('Finish');
 28  END;
 29  /

Start
Finish

PL/SQL procedure successfully completed

如果您在第 16 行注释掉提交,则输出将是:

Start
TEST1
Finish

PL/SQL procedure successfully completed

【讨论】:

Metalink Bug 10425196 正是我们遇到的问题,感谢您挖掘这个问题!我还无法使用 DBMS_FLASHBACK 测试您的解决方法,因为我们的本地测试实例没有启用它。我认为我们无法使用此解决方法,因为我们的客户托管他们自己的 Oracle 实例,我们无法保证他们运行的版本,是否启用或如何配置,而且我们必须找到并修改所有受影响的代码。我认为我们将不得不拭目以待,看看 Oracle 是否会发布错误修复程序,并可能会进入列表以对他们的补丁进行 beta 测试。 在原始帖子中添加了说明,说明为什么我无法删除 COMMIT【参考方案3】:

来自 Metalink(又名 Oracle 支持)

状态错误 10425196:92 - 已关闭,不是错误

问题:

调用返回 REF CURSOR 的存储过程时,不同的行为 可见于 10.2.0.5 及更早版本与 11.1.0.6 及更高版本的数据库。

事件顺序

    调用存储过程传入​​ Ref 游标 针对 TableA 打开参考光标 从存储过程中更新 TableA 中的一些数据 提交更新 过程执行结束,将 Ref Cursor 返回给调用者

10.2.0.5 及更早版本

返回的游标看不到更新的数据,因为它是在之前打开的 正在更新的数据。这是预期的行为。

11.1.0.6 及更高版本

返回的游标看到更新的数据并返回更新的数据 与 10.2.0.5 及更早版本的行为不同。

诊断分析:

10.2.0.4 Windows 预期行为 10.2.0.5 Solaris 预期行为 11.1.0.6 Solaris 意外行为 11.1.0.7 Windows 意外行为 11.2.0.1 Solaris 意外行为 11.2.0.2 Solaris 意外行为

相关错误:

没有找到。

如有必要,您可以恢复到 10.2.0.5 之前的行为,设置以下启动参数并重新启动数据库。

_row_cr = 假

【讨论】:

以上是关于存在唯一索引时,Oracle 10g 和 11g 之间 REF CURSOR 的行为不同?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle 10g,和 Oracle 11g 能互相兼容吗?还是向下兼容?

oracle 10g和11g客户端可以共存吗

oracle 10g和11g客户端可以共存吗

oracle 10g,11g,是啥意思????是否它的大小??有10或11G那么大?

使用 oracle 11g 客户端和 10g 服务器可以吗?

Oracle 数据库 10g 和 11g 中选择更新 ...的区别