Подтвердить что ты не робот

Различное поведение для REF CURSOR между Oracle 10g и 11g при наличии уникального индекса?

Описание

У меня есть хранимая процедура 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 - false, а 1 - истина), в котором указывается, были ли эти данные еще обработаны.

Вид ограничивается только отображением строк, которые не были обработаны (представление также присоединяется к некоторым другим таблицам, делает некоторые встроенные запросы и вызовы функций и т.д.). Таким образом, в момент, когда курсор открывается, представление показывает одну или несколько строк, а затем, после того, как курсор открыт, выполняется инструкция обновления, чтобы перевернуть флаг в таблице заголовков, после чего завершается процедура фиксации.

В 10g курсор открывается, он содержит строку, а затем оператор обновления переворачивает флаг и второй раз не выполняет никаких данных.

В 11g курсор never содержит строку, это как если бы курсор не открывался до тех пор, пока не выполнится оператор обновления.

Я обеспокоен тем, что что-то изменилось в 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:
  начать первый тест
  TEST1
  конец первого теста
  начать второй тест
  TEST1
  конец второго теста

Пример того, что будет выводиться на 11g:
  начать первый тест
  TEST1
  конец первого теста
  начать второй тест
  конец второго теста

Разъяснение

Я не могу удалить COMMIT, потому что в сценарии реального мира процедура вызывается из веб-приложения. Когда поставщик данных на лицевой стороне вызывает процедуру, он будет вызывать неявный COMMIT при любом отключении от базы данных. Поэтому, если я удалю COMMIT в процедуре, то да, анонимная демонстрация блоков будет работать, но сценарий реального мира не будет, потому что COMMIT все равно случится.

Вопрос

Почему 11g ведут себя по-другому? Есть ли что-нибудь, что я могу сделать, кроме повторной записи кода?

4b9b3361

Ответ 1

Обнаружена ошибка, обнаруженная сравнительно недавно. Metalink Bug 1045196 описывает точную проблему. Надеюсь, скоро будет выпущен патч. Для тех из вас, кто не может пройти мимо стены Металинк, вы найдете несколько деталей:

Metalink

Ошибка 10425196: PL/SQL RETURNING REF CURSOR ACTS РАЗЛИЧНО ВКЛЮЧЕНЫ 11.1.0.6 VS 10.2.0.5

Тип: Дефект
Серьезность: 2 - Сильная потеря обслуживания
Статус: ошибка кода Создано: 22-Dec-2010

ДИАГНОСТИЧЕСКИЙ АНАЛИЗ из оригинальной заявки:
- 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 Un-Expected Behavior

Дополнительная информация

Изменение параметра OPTIMIZER_FEATURES_ENABLE = '10.2.0.4 ' не решает проблему. Таким образом, похоже, что это связано скорее с изменением дизайна в двигателе базы данных 11g, а не с настройкой оптимизатора.

Оболочка кода

Это, по-видимому, является результатом использования индекса при запросе таблицы, а не акта обновления таблицы и/или фиксации. Используя мой пример выше, вот два способа обеспечить, чтобы запрос не использовал индекс. Оба могут повлиять на производительность запроса.

Воздействие производительности запроса может быть временно приемлемым до тех пор, пока не будет выпущен патч, но я полагаю, что использование FLASHBACK в качестве предлагаемого @Edgar Chupit может повлиять на производительность всего экземпляра (или может быть недоступно в некоторых случаях), так что опция может быть неприемлемым для некоторых. В любом случае, на данный момент изменения кода времени являются единственным известным способом обхода.

Способ 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

В нашем случае, учитывая наши клиентские среды и поскольку он изолирован одной хранимой процедурой, мы продолжим использовать наш метод обхода кода, чтобы предотвратить появление каких-либо неизвестных побочных эффектов в масштабе всего другого приложения и пользователей.

Ответ 2

Это действительно странная проблема, спасибо за обмен!

Это действительно похоже на изменение поведения в Oracle, начиная с Oracle 11.1, и даже подтвержденная ошибка с аналогичной проблемой на metalink (ошибка # 10425196). К сожалению, на данный момент информации о metalink по теме mater нет, но я также открыл SR с Oracle, предлагая предоставить дополнительную информацию.

Пока я не могу дать вам объяснение, почему это происходит, и если есть (скрытый) параметр, который может изменить это поведение до стиля 10g, я думаю, что могу предоставить вам обходной путь. Вы можете использовать функциональность Oracle backback query, чтобы заставить 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

Ответ 3

От Metalink (aka Поддержка Oracle)

Ошибка состояния 10425196: 92 - закрыто, а не ошибка

ПРОБЛЕМА:

При вызове хранимой процедуры, которая возвращает REF CURSOR, различное поведение рассматривается в 10.2.0.5 и более ранних версиях с 11.1.0.6 и более поздними базами данных.

Последовательность событий

  • Вызов хранимой процедуры, проходящей в курсоре Ref
  • Открыть курсор Ref на TableA
  • Обновление некоторых данных внутри TableA из хранимой процедуры
  • COMMIT обновление
  • Выполнение процедуры завершается возвратом 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 = false