Триггеры Oracle-проблема с мутирующими таблицами
мои таблицы:
TableA (id number, state number)
TableB (id number, tableAId number, state number)
TableC (id number, tableBId number, state number)
таким образом, элементы в TableC-это дети TableB, а элементы в TableB-дети TableA. И наоборот-элементы в TableA-это родители TableB, а элементы в TableB-родители TableC.
Я хотел бы контролировать состояние родительских элементов... допустим, например, что у нас есть такие данные:
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 (set state = 50, где id = 1), а затем обновлять также TableA (set state = 50, где id = 1)
Я хотел бы сделать это с триггерами (после обновления, вставки, удаления, на TableA, TableB, TableC), так что после каждого действия эти шаги будут выполняться:
- получить ID родителя
- найти наименьшее состояние из всех детей текущего родителя
- если наименьшее состояние всех детей больше, чем состояние родителя, затем обновить родитель
как я могу избежать "мутирующей ошибки таблицы"? Можно ли в этом примере использовать автономные транзакции? Я видел некоторые мнения, что мутирующая ошибка таблицы указывает на недостатки в логике приложения - это правда и как я могу изменить свою логику, чтобы предотвратить эту ошибку?
спасибо
изменить: Спасибо за все отличные ответы!
В конце концов, я использовал триггеры (спасибо Винсент Мальграт, который указал на Тома Статья кайта).
изменить: В реальном конце я использовал хранимые процедуры и удалил триггеры:)
8 ответов
Как вы заметили, будет трудно ответить на ваши бизнес-требования с триггерами. Причина в том, что Oracle мая обновить / вставить таблицы с несколькими потоками одновременно для одного запроса (параллельный DML). Это означает, что ваш сеанс не может запросить таблицу, которую он обновляет пока происходит обновление.
Если вы действительно хотите сделать это с помощью триггеров вы должны следовать вид логики, показанной в этой статье Том Кайт!--8-->. Как видите, это не так просто.
есть еще один, более простой, элегантный, простой в обслуживании метод: использовать процедуры. Отмените право обновления / вставки для пользователя(пользователей) приложения и напишите набор процедур, которые позволяют приложению обновлять столбцы состояния.
эти процедуры будут содержать блокировку родительской строки (чтобы предотвратить несколько сеансов для изменения одного и того же набора строк) и применят вашу бизнес-логику в эффективный, читаемый и легко ремонтируемым способом.
вы не должны imho использовать триггеры для сложной бизнес-логики. Переместите его в сохраненный proc (пакет PL/SQL) или клиентский код. Приложения с большим количеством триггеров становятся unmaintanable потому что вы потеряете любое чувство "последовательности действий" очень скоро.
использование автономных транзакций абсолютно небезопасно, используйте автономную транзакцию только для ведения журнала, трассировки, отладки и, возможно, аудита.
читать: http://www.oracle.com/technetwork/issue-archive/2008/08-sep/o58asktom-101055.html
здесь вы можете прочитать, как вы можете решить проблему, когда вы хотите использовать триггеры без использования автономных транзакций:http://www.procaseconsulting.com/learning/papers/200004-mutating-table.pdf
вы можете выполнить рефакторинг решение включить мнения, чтобы выполнить расчет ?
CREATE VIEW a_view AS
SELECT a.Id, min(b.State) State FROM tableA,tableB
WHERE a.Id=b.tableAId
GROUP BY a.Id;
Я согласен, что сохраненные процессы (как предлагается здесь, в других сообщениях) также являются хорошим кандидатом, но обратите внимание, что представление будет автоматически обновляться, в то время как я считаю, что вам придется запланировать запуск хранимых процессов, чтобы сохранить данные "в синхронизации": что может быть хорошо - это зависит от ваших требований.
Я думаю, другой вариант-создать некоторые функции для выполнения расчет, но лично я бы предпочел подход с точки зрения (при прочих равных условиях).
Я видел некоторые мнения, что мутирует ошибка в таблице указывает на недостатки в логике
приложение-это правда и как я могу изменить свою логику, чтобы предотвратить это
ошибка?
Я не знаю, где вы это видели, но я знаю, что опубликовал это мнение много раз до этого.
почему я думаю, что мутирующие таблицы обычно указывают на недостаток в модели данных? Потому что вид "требования", который управляет кодом, который бросает Ора-4091 часто связано с плохой конструкцией, особенно недостаточной нормализацией.
Вы сценарий является классическим примером этого. Вы получаете ORA-04091, потому что вы выбираете из TableC
при вставке или обновлении. Но почему вы выбираете из TableC
? Потому что вам" нужно " обновить столбец на его родителе,TableB
. Но эта колонка-избыточная информация. В полностью нормализованной модели данных этот столбец не будет существовать.
Denormalisation является часто рекламируется как механизм повышения производительности запросов. К сожалению, сторонники денормализации замалчивают ее стоимость, которая оплачивается в валюте чрезмерной сложности при вставке, обновлении и удалении.
Итак, как вы можете изменить вашу логику? Простой ответ-отбросить столбцы и не беспокоиться о сохранении наименьшего состояния по родительскому идентификатору. Вместо этого выполните MIN()
запрос всякий раз, когда вам нужна эта информация. Если вам это нужно часто, и это будет дорого выполнить запрос, то вы создаете материализованные представления, которые хранят данные (обязательно используйте ENABLE QUERY REWRITE
)
вы можете использовать оба триггера и ограничения целостности для определения и применять любой тип правила целостности. Тем не менее, Oracle Corporation сильно рекомендует использовать триггеры для ограничивать ввод данных только в следующие ситуации:
для обеспечения ссылочной целостности, когда дочерние и родительские таблицы включены различные узлы распределенного база данных для обеспечения комплексного ведения бизнеса правила не определима через целостность ограничения при требуемый правило ссылочной целостности не может быть принудительно с использованием следующей целостности ограничения:
- NOT NULL, UNIQUE
- ПЕРВИЧНЫЙ КЛЮЧ
- ВНЕШНИЙ КЛЮЧ
- Регистрация
- УДАЛИТЬ КАСКАД
- УДАЛИТЬ НАБОР NULL
в качестве примера того, почему ваша логика потерпит неудачу, возьмите сценарий, где родитель A имеет запись 1 с дочерними записями 1A и 1B. Состояние 1A равно 10, а 1B-15, поэтому вы хотите, чтобы ваш родитель был 10.
теперь кто-то обновляет состояние 1A до 20, в то же время кто-то удаляет 1B. Поскольку удаление 1B не зафиксировано, обновление транзакции 1A по-прежнему будет видеть 1B и захочет установить состояние родителя в 15, в то время как удаление транзакции 1B будет видеть старое незафиксированное значение 1A и будет хотеть, чтобы родительское состояние было 10.
Если вы де-нормализуете это, вы должны быть очень осторожны с блокировкой, чтобы перед вставкой/обновлением/удалением любых дочерних записей родительская запись была заблокирована, выполните свои изменения, выберите все дочерние записи, обновите родительскую, а затем зафиксируйте, чтобы освободить блокировки. Хотя это можно сделать с помощью триггеров, лучше всего использовать хранимую процедуру.
делать такие вещи, как это большой соблазн, и если вы будете следовать предложениям в статье Тома кайта, на которую ссылаются другие, это возможно. Однако, если что-то can быть сделано, не значит, что он должны быть сделано. Я настоятельно рекомендую реализовать что-то подобное в качестве хранимой процедуры/функции/пакета. Сложная логика такого рода не должна выполняться с использованием триггеров, несмотря на очевидные соблазны, поскольку она значительно повышает сложность системы без соответствующего увеличения полезности. Иногда мне приходится работать с таким кодом, и это не радость.
удачи.
Не используйте автономные транзакции, или вы получите очень интересные результаты.
чтобы избежать проблемы мутирующих таблиц, вы можете сделать следующее:
после вставки, обновления или удаления для каждого триггера строки найдите Родительский идентификатор и сохраните его в коллекции PL/SQL (внутри пакета). Затем в триггере after INSERT или UPDATE или DELETE (уровень инструкции, без части "для каждой строки") прочитайте родительские идентификаторы из коллекции PL/SQL и обновите родительская таблица соответственно.