Вы не любите кошек? Да вы просто не умеете их готовить! (с) Альф
При проектировании достаточно объёмных реляционных баз данных часто принимается решение об отступлении от нормальной формы — «денормализации».
Причины могут быть разными. От попытки ускорения доступа к определённым данным, ограничений используемой платформы/фреймворка/средств разработки и до недостатка квалификации разработчика/проектировщика БД.
Впрочем, строго говоря, ссылка на ограничения фремфорка и т.п. — по сути попытка оправдать недостаток квалификации.
Денормализованные данные — слабое звено, через которое легко можно привести нашу базу в неконсистентное (нецелостное) состояние.
Что с этим делать?
Пример
В базе данных есть таблица с какими-то финансовыми операциями: поступление и списание средств по разным счетам.
Нужно всегда знать остаток средств на счёте.
В нормализованных данных остаток средств — всегда рассчитываемая величина. Суммируем все поступления минус списания.
Однако, когда количество операций ну очень большое, то каждый раз рассчитывать остаток слишком затратно.
Поэтому принято решение хранить актуальные остатки в отдельной таблице. Как обновлять данные в этой таблице?
Решение «как обычно»
Практически во всех информационных системах, с которыми мне приходилось работать, эту задачу выполняло внешнее приложение, в котором реализована бизнес логика. Хорошо, если приложение несложное и точек изменения данных — одна, из формы в пользовательском интерфейсе. А если есть какие-то импорты, API, сторонние приложения и так далее? И эти вещи делают разные люди, команды? А если не одна таблица с итогами, а их несколько в разных разрезах? А если ещё и не одна таблица с операциями (встречал и такое)?
Тут уследить за тем, что разработчик при обновлении операции не забыл обновить ещё кучку таблиц, становится всё сложнее и сложнее. Данные теряют целостность. Остатки по счёту не соответствуют операциям. Конечно, тестирование должно выявить такие ситуации. Но мы живём не в таком идеальном мире.
Кошки Триггеры
В качестве альтернативы для контроля целостности денормализованных данных «взрослой» СУБД используют триггеры.
Часто приходилось слышать, что триггеры жутко замедляют базу данных, поэтому их использование нецелесообразно.
Вторым аргументом было то, что вся логика находится в отдельном приложении и держать бизнес логику в разных местах тоже нецелесообразно.
Давайте разберёмся.
Тормоза
Триггер срабатывает внутри транзакции, изменяющей данные в таблице. Транзакция не может быть завершена, если триггер не произвёл необходимых действий. Отсюда вывод: триггеры должны быть как можно «легче». Пример «тяжёлого» запроса в триггере:
update totals
set total = select sum(operations.amount) from operations where operations.account = current_account
where totals.account = current_account
Запрос обращается к таблице операций (operations) и суммирует все суммы операций (amount) для счёта (account).
Такой запрос с ростом базы данных будет съедать всё больше и больше времени и ресурсов. Но того же результата можно добиться, используя «лёгкий» запрос типа:
update totals
set total = totals.total + current_amount
where totals.account = current_account
Такой триггер при добавлении новой строки просто увеличит итог по счёту, не рассчитывая его заново, он не зависит от объёма данных в таблицах. Рассчитывать итог заново нет смысла, так как мы можем быть уверены, что триггер срабатывает ВСЕГДА при добавлении новой операции.
Аналогично обрабатывается удаление и изменение строк. Такого типа триггеры практически не замедлят операции, но будут гарантировать связность и целостностность данных.
Всегда, когда я наблюдал «тормоза» при вставке данных в таблицу с триггером, это был образчик такого «тяжёлого» запроса. И в подавляющем большинстве случаев удавалось переписать его в «лёгком» стиле.
Бизнес логика
Тут стоит отделить мух от котлет. Есть смысл отличать функции, обеспечивающие целостность данных, от собственно бизнес логики. В каждом таком случае задаю вопрос: если бы данные были нормализованы, то была бы нужна такая функция? Если ответ положительный — это бизнес логика. Отрицательный — обеспечение целостности данных. Смело заворачивайте эти функции в триггеры.
Впрочем, есть мнение, что всю бизнес логику легко можно реализовать средствами современной СУБД, такой как PostgreSQL или Oracle. Подтверждение нахожу в своём just-for-fun проекте.
Надеюсь, эта статья поможет уменьшить количество багов в вашей информационной системе.
Конечно, я далёк от мысли, что всё здесь написанное, является истиной в последней инстанции. В реальной жизни, конечно же, всё сложнее. Поэтому решения в каждом конкретном случае принимать вам. Используйте своё инженерное
Автор: zoroda