Триггеры Oracle - проблема с изменяющимися таблицами - PullRequest
6 голосов
/ 26 января 2010

Мои таблицы:

TableA (id number, state number)
TableB (id number, tableAId number, state number)
TableC (id number, tableBId number, state number)

Таким образом, элементы в TableC являются дочерними элементами TableB, а элементы в TableB являются дочерними элементами TableA. И наоборот - элементы в таблице А являются родителями таблицы В, а элементы в таблице В являются родителями таблицы С.

Я бы хотел контролировать состояние родительских элементов ... скажем, например, что у нас есть эти данные:

TableA (id, state): 
1, 40

TableB (id, tableAId, state): 
1, 1, 40
2, 1, 60

TableC (id, tableBId, state): 
1, 1, 40
2, 1, 50
3, 2, 60
4, 2, 70

Родительский штат должен всегда иметь малейшее состояние его детей. Так что, если мы сейчас обновим TableC следующим образом:

update TableC set state = 50 where Id = 1;

мой триггер должен автоматически обновить TableB (установить состояние = 50, где id = 1), а затем обновить также TableA (установить состояние = 50, где id = 1)

Я хотел бы сделать это с помощью триггеров (AFTER UPDATE, INSERT, DELETE, для TableA, TableB, TableC), чтобы после каждого действия выполнялись следующие шаги:

  1. получить идентификатор родителя
  2. найти наименьшее состояние из всех дочерних элементов текущего родителя
  3. если наименьшее из всех дочерних состояний больше, чем родительское, обновите родительский

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

Спасибо


EDIT: Спасибо за отличные ответы!

В конце концов, я использовал триггеры (спасибо Винсенту Малграту, который указал на статью Тома Кайта).


EDIT: В РЕАЛЬНОМ КОНЦЕ я использовал хранимые процедуры и удалил триггеры:)

Ответы [ 8 ]

12 голосов
/ 26 января 2010

Как вы заметили, будет сложно ответить на ваши бизнес-требования с помощью триггеров. Причина в том, что Oracle может обновлять / вставлять таблицы с более чем одним потоком одновременно для одного запроса (параллельный DML). Это означает, что ваш сеанс не может запросить таблицу, которую он обновляет , пока обновление происходит .

Если вы действительно хотите сделать это с помощью триггеров, вам придется следовать логике , показанной в этой статье Томом Кайтом . Как видите, это не что-то простое.

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

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

5 голосов
/ 26 января 2010

Имхо не следует использовать триггеры для сложной бизнес-логики. Переместите его в сохраненный процесс (пакет PL / SQL) или код клиента. Приложения с большим количеством триггеров становятся неосуществимыми, потому что вы очень скоро утратите чувство «последовательности действий».

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

Чтение: http://www.oracle.com/technetwork/issue-archive/2008/08-sep/o58asktom-101055.html

Здесь вы можете прочитать, как вы можете решить проблему, когда хотите использовать триггеры без использования автономных транзакций: http://www.procaseconsulting.com/learning/papers/200004-mutating-table.pdf

3 голосов
/ 26 января 2010

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

Я не знаю, где вы это видели, но я знаю, что опубликовал это мнение много раз прежде.

Почему я думаю, что изменяющиеся таблицы обычно указывают на изъян в модели данных? Потому что своего рода «требование», определяющее код, который нарушает работу ORA-4091, часто связано с плохим дизайном, особенно с недостаточной нормализацией.

Ваш сценарий является классическим примером этого. Вы получаете ORA-04091, потому что вы выбираете из TableC при вставке или обновлении. Но почему вы выбираете из TableC? Поскольку вам «необходимо» обновить столбец на его родительском элементе, TableB. Но этот столбец является избыточной информацией. В полностью нормализованной модели данных этот столбец не существует.

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

Итак, как вы можете изменить свою логику? Простой ответ - отбросить столбцы и не беспокоиться о сохранении наименьшего состояния по родительскому идентификатору. Вместо этого выполните запрос MIN() всякий раз, когда вам нужна эта информация. Если вам это нужно часто и выполнение запроса будет дорогостоящим, вы создаете материализованные представления, в которых хранятся данные (обязательно используйте ENABLE QUERY REWRITE)

3 голосов
/ 26 января 2010

Можете ли вы реорганизовать решение для включения представлений для выполнения расчета?

CREATE VIEW a_view AS
SELECT a.Id, min(b.State) State FROM tableA,tableB
WHERE a.Id=b.tableAId
GROUP BY a.Id;

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

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

2 голосов
/ 27 января 2010

В качестве примера того, почему ваша логика потерпит неудачу, рассмотрим сценарий, в котором у PARENT A есть запись 1 с записями CHILD 1A и 1B. СОСТОЯНИЕ 1А - 10, а 1В - 15, поэтому вы хотите, чтобы ваш родитель был 10.

Теперь кто-то обновляет СОСТОЯНИЕ от 1A до 20, в то время как кто-то удаляет 1B. Поскольку удаление 1B является незафиксированным, обновление транзакции 1A по-прежнему будет видеть 1B и будет хотеть установить состояние родительского элемента равным 15, тогда как транзакция удаления 1B увидит старое незафиксированное значение 1A и будет хотеть, чтобы родительское состояние 10.

Если вы де-нормализуете это, вы должны быть очень осторожны с блокировкой, чтобы, ДО вставки / обновления / удаления любых дочерних записей, родительская запись была заблокирована, выполнить ваши изменения, выбрать все дочерние записи, обновить родитель, а затем совершить снимать блокировки. Хотя это можно сделать с помощью триггеров, лучше всего использовать хранимую процедуру.

2 голосов
/ 26 января 2010

Вы можете использовать как триггеры, так и ограничения целостности, чтобы определить и применять правила целостности любого типа. Тем не менее, Oracle Corporation сильно рекомендует использовать триггеры для ограничить ввод данных только в следующие ситуации:

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

  • НЕ NULL, УНИКАЛЬНО
  • ПЕРВИЧНЫЙ КЛЮЧ
  • ИНОСТРАННЫЙ КЛЮЧ
  • ПРОВЕРКА
  • УДАЛИТЬ КАСКАД
  • УДАЛИТЬ SET NULL

источник: Oracle9i Database Concepts

1 голос
/ 26 января 2010

Не используйте автономные транзакции, иначе вы получите очень интересные результаты.

Чтобы избежать проблемы с таблицами мутаций, вы можете сделать следующее:

В триггере ПОСЛЕ ВСТАВКИ ИЛИ ОБНОВЛЕНИЯ ИЛИ УДАЛЕНИЯ ДЛЯ КАЖДОЙ СТРОКИ найдите родительский идентификатор и сохраните его в коллекции PL / SQL (внутри ПАКЕТА). Затем в поле ПОСЛЕ ВСТАВКИ ИЛИ ОБНОВЛЕНИЯ ИЛИ УДАЛЕНИЯ TRIGGER (на уровне операторов, без части «для каждой строки») прочитайте родительские идентификаторы из коллекции PL / SQL и обновите родительскую таблицу соответствующим образом.

1 голос
/ 26 января 2010

Делать подобные вещи - великое искушение, и если вы будете следовать советам, приведенным в статье Тома Кайта, на которые ссылаются другие, это возможно. Однако, если что-то можно сделать, это не значит, что должно быть . Я настоятельно рекомендую вам реализовать нечто подобное в виде хранимой процедуры / функции / пакета. Сложная логика такого рода не должна выполняться с использованием триггеров, несмотря на очевидные соблазны, потому что это значительно повышает сложность системы без соответствующего увеличения полезности. Время от времени мне приходится работать над таким кодом, и это не радость.

Удачи.

...