Периодическая проблема с пакетом Oracle, возвращающим пустые таблицы - PullRequest
2 голосов
/ 03 февраля 2012

У нас есть очень сложный пакет Oracle (более 4100 строк кода), который вызывает у нас проблемы. Мне было поручено отследить проблему. Проблема в том, что когда мы вызываем процедуру execute_filter, мы ожидаем получить 6 таблиц. Тем не менее, примерно 10-15 раз в день наш код падает, потому что индекс таблицы 1 находится вне диапазона. Когда это повторяется, кажется, что оно повторяется несколько раз, а через минуту снова отлично работает. Я все еще не смог воспроизвести это в отладчике, чтобы точно узнать, что это за набор данных, но у меня есть теория, что набор данных - это просто одна пустая таблица.

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

Метод execute_filter вызывает один или несколько десятков других методов, например filter_by_areas_name. Каждый из этих методов запрашивает некоторые данные и вставляет эти данные в таблицу с именем tpm_temp_filter_project. Примером этого является:

FOR I IN 1..areaState.COUNT LOOP
   INSERT INTO tpm_temp_filter_project
   (
      projectid,
      versionid
   )
   SELECT .. --Grabs the data it needs from other tables

В конце каждого из этих вызовов фильтра мы вызываем процедуру с именем populate_result_table, которая копирует данные из tpm_temp_filter_project в другую таблицу и затем выполняет:

EXECUTE IMMEDIATE 'truncate table tpm_temp_filter_project';

Итак, моя теория состоит в том, что если два человека выполняют этот запрос одновременно, строки из этих таблиц "держателей" преждевременно усекаются, в то время как другой запрос все еще нуждается в них.

Как лучше всего предотвратить подобные вещи? Одна идея, которая у меня была, заключалась в следующем:

LOCK TABLE tpm_temp_filter_project IN EXCLUSIVE MODE;

В самом начале execute_filter и COMMIT; в качестве самой последней строки. Теоретически, это должно позволить только одному человеку выполнять команду одновременно, и ожидающие запросы будут «блокироваться» до тех пор, пока не будет выполнен первый фильтр. Я еще не пробовал, но у меня есть несколько вопросов.

  1. Это хорошая теория относительно того, что происходит?
  2. Это хорошее исправление или есть лучшее решение этой проблемы?

Я ценю любое понимание этой проблемы.

UPDATE:

Вот схема для временной таблицы:

CREATE GLOBAL TEMPORARY TABLE TPMDBO.TPM_TEMP_FILTER_PROJECT  ( 
    PROJECTID   NUMBER NULL,
    VERSIONID   NUMBER NULL 
    )
ON COMMIT DELETE ROWS

ДРУГОЕ ОБНОВЛЕНИЕ:

Это, похоже, не конфликт между двумя сессиями. Если я изменюсь:

EXECUTE IMMEDIATE 'truncate table tpm_temp_filter_project';

до:

DELETE tpm_temp_filter_project;

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

Второе доказательство - я, наконец, повторил ошибку в отладчике Visual Studio. DataSet в .NET полностью пуст. Есть одна таблица с именем table , которая имеет нулевые столбцы. Если бы это была проблема с одним сеансом, удаляющим данные в этих временных таблицах, то я ожидал бы, что допустимая схема с нулевыми строками или, возможно, строками из неправильного сеанса.

Ответы [ 5 ]

2 голосов
/ 21 марта 2012

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

В коде вызывается процедура, которая хранит некоторые данные в локальной переменной. После этого некоторый код C # был запущен для внутреннего использования, и в итоге снова вызвал Open() для соединения с базой данных (даже если соединение уже было открыто). Похоже, что вместо no-op вызов Open() снова закрывает и заново открывает соединение с базой данных - по крайней мере, с теми драйверами Oracle, которые мы используем. 99 из 100 раз он просто выберет одно и то же соединение из пула соединений и продолжит нормально работать. Однако время от времени он выбирает другое соединение, и идентификатор нашего сеанса меняется, и состояние пакета теряется.

Комментируя, что Open() call немедленно решает проблему.

2 голосов
/ 04 февраля 2012

truncate - это DDL, поэтому он автоматически выдает коммит, поэтому вы видите эффекты между сеансами.

Самое очевидное и лучшее решение (как упомянуто в комментарии @Гленн) должен использовать глобальную временную таблицу с on commit delete rows.Это гарантирует, что данные в этой таблице будут существовать только на время транзакции, в которой они созданы.

Если вам нужно охватить транзакции, вы можете использовать глобальную временную таблицу с on commit preserve rows, нотогда вам нужно будет убедиться, что вы используете один сеанс для всего доступа к этим данным.Если вы завершите сеанс в конце обработки, данные будут удалены автоматически.Однако, если вы повторно используете сеанс (то есть вы используете пул сеансов или что-то подобное), вам нужно будет удалить всю таблицу в конце обработки.

Другое решение - использовать delete, а нечем truncate.delete будет ограничивать изменения в текущем сеансе, пока не будет выпущено commit.Если вы insert и delete в одной и той же транзакции, эффект будет во многом таким же, как при использовании глобальной временной таблицы.

Решение, о котором вы спрашиваете, явно блокируя всю таблицу, также должно работать,Тем не менее, вы можете обнаружить, что с этим решением страдает производительность, поскольку вы будете эффективно сериализовать свою обработку.Другие решения не имеют этого ограничения.


Исходя из пересмотренного вопроса:

Поскольку вы уже используете глобальную временную таблицу (GTT),большинство моих ответов становится неактуальным.Казалось бы, теперь возникает вопрос «почему ГТТ очищается преждевременно».Поскольку вы используете on commit delete rows, вероятный ответ заключается в том, что что-то вызывает преждевременное завершение вашей транзакции.Ищите вложенное commit или rollback, где оно будет выполняться только изредка.Другим очевидным виновником может быть выполнение DDL через execute immediate, например, сброс последовательности.

Если вы не можете найти что-то подобное, вы можете попробовать изменить GTT на on commit preserve rows.Это позволит данным в GTT сохраняться между транзакциями и, тем не менее, должно оставаться безопасным, если ваш процесс завершит свою сессию после завершения.

1 голос
/ 04 февраля 2012

Итак, исходя из ваших обновлений, у вас есть глобальная временная таблица (GTT).

Так как это GTT, вы должны не обрезать его. Загрузите в него данные, обработайте данные, и когда вы закончите, просто сделайте коммит. Поскольку он определен как «при удалении строк при фиксации», нет необходимости в усечении, и фиксация также лучше с точки зрения производительности / масштабируемости.

Поскольку это GTT, кстати, область / видимость данных находится на уровне сеанса. То есть все, что записывается в таблицу конкретным сеансом, может быть просмотрено только этим сеансом.

Итак, ваши предыдущие комментарии о двух людях, запрашивающих одновременно, неверны. Каждый сеанс будет видеть свой уникальный набор данных.

Надеюсь, это поможет.

0 голосов
/ 04 февраля 2012

Это должно быть хорошо.

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

0 голосов
/ 04 февраля 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...