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

标签 oracle stored-procedures plsql oracle10g oracle11g

描述

我有一个 Oracle 存储过程,它在本地开发实例上以及运行 Oracle 8、然后是 9、然后 10 和最近 11 的多个客户端测试和生产实例上运行了 7 年左右。它一直运行,直到升级到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 上运行)。

具体代码要复杂得多,但要以某种现实的概述来解释问题:我在 header 表和一堆子表中有一些数据,这些数据将输出为 PDF。 header 表有一个 bool 值(NUMBER(1),其中 0 为假,1 为真)列,指示该数据是否已被处理。

该 View 仅限于显示尚未处理的行(该 View 还连接其他一些表,进行一些内联​​查询和函数调用等)。因此,当游标打开时, View 显示一行或多行,然后在游标打开后运行更新语句以翻转 header 表中的标志,发出提交,然后过程完成。

在 10g 上,游标打开,其中包含该行,然后更新语句翻转标志,第二次运行该过程将不会产生任何数据。

在 11g 上,游标从不包含该行,就好像游标直到更新语句运行后才打开。

我担心 11g 中可能发生了一些变化(希望是可以配置的设置),这可能会影响其他过程和其他应用程序。我想知道是否有人知道为什么两个数据库版本之间的行为不同以及是否可以在不更改代码的情况下解决问题。

更新 1: 我设法将问题追溯到一个独特的约束。看来,当 11g 中存在唯一约束时,无论我是针对实际对象还是针对以下简单示例运行现实世界的代码,问题都可以 100% 重现。

更新 2: 我能够从方程中完全消除 View 。我更新了简单的示例,以表明即使直接针对表查询也存在问题。

简单示例

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;

匿名区 block 演示

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,那么是的,匿名 block 演示将起作用,但现实世界的场景不会,因为 COMMIT 仍然会发生。

问题

为什么 11g 的行为有所不同?除了重写代码之外我还能做些什么吗?

最佳答案

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

Metalink

错误 10425196:PL/SQL 返回引用游标在 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)提示,但这可能比使用方法 1 更减慢查询速度。

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


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

Oracle 支持人员最终通过以下 Metalink 更新进行了回应:

Oracle Support - July 20, 2011 5:51:19 AM GMT-07:00 [ODM Proposed Solution(s)]
Development has reported this will be a significant issue to fix and 
has suggested that the following workaround be applied:

edit init.ora/spfile with the following undocumented parameter:

"_row_cr" = false

Oracle Support - July 20, 2011 5:49:20 AM GMT-07:00 [ODM Cause Justification]
Development has determined this to be a defect

Oracle Support - July 20, 2011 5:48:27 AM GMT-07:00 [ODM Cause Determination]
Cause has been traced to a row source cursor optimization

Oracle Support - July 20, 2011 5:47:27 AM GMT-07:00 [ODM Issue Verification]
Development has confirmed this to be an issue in 11.2.0.1

经过一些进一步的通信,听起来似乎这并没有被视为一个错误,而是一个向前推进的设计决策:

Oracle Support - July 21, 2011 5:58:07 AM GMT-07:00 [ODM Proposed Solution Justif]
From 10.2.0.5 onward (which includes 11.2.0.2) we have an optimization called
ROW CR it is only applicable to queries which use an unique index to
determine the row in the table.

A brief overview of this optimization is that we try to avoid rollbacks while
constructing a CR block if the present block has no uncommitted changes.

So the difference seen in 11.2.0.2 is because of this optimization. The
suggested workaround is to turn off of this optimization so that things will
work exactly as they used to work in 10.2.0.4

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

关于oracle - 当存在唯一索引时,Oracle 10g 和 11g 之间的 REF CURSOR 行为有何不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4598725/

相关文章:

java - 为 Oracle PL/SQL 包生成 JDBC 代码

java - 使用本地过程将数据插入数据库或使用最近的框架(如 spring 或 Entity Framework )将数据插入数据库之间有什么区别吗?

linux - Pro*C中如何获取oracle查询状态?

java - Oracle:OALL8 处于不一致状态

sql - 使用存储过程在同一数据库中以一种方式同步两个表

sql - 获取多行的查询需要逗号分隔的输出

oracle - 过程中的截断和插入不能一起工作

oracle - SQL Developer 的自动跟踪中 tkprof 和 v$statname 的等效列是什么?

SQL优化: deletes taking a long time

sql-server - 与通过 SSMS 手动执行相比,通过 SSIS(通过 SQL 代理作业)存储过程运行速度慢 24,000%