Ни для кого не секрет, что разработчики не принимают во внимание проблемы масштабируемости, создавая решения, поддерживаемые SQL Server или другими реляционными базами данных. В самом деле, не так давно мне пришлось жаловаться на разработчиков, которые ошибочно считают NoSQL решением всех проблем, связанных с масштабируемостью и производительностью, с которыми они сталкиваются при создании приложений, основанных на реляционных базах данных. Тем не менее, по моему опыту, разработчикам проще всего прочувствовать проблемы, связанные с масштабируемостью, благодаря триггерам.
Проблемы с триггерами
Триггеры представляют собой специализированный код, который срабатывает при инициирующем событии внутри базы данных. В самом SQL Server есть два основных типа триггеров: триггеры Data Definition Language, или языка определения данных (DDL), и Data Manipulation Language, или языка управления данными (DML). Триггеры DDL, как следует из названия, работают в том случае, когда выполняются события языка определения данных (например, создание нового года, структур, имен входа и т.д.). Этот вид триггеров мы здесь рассматривать не будем. Нас интересуют триггеры DML, которые выполняются при изменении данных или управлении ими, в контексте перечисленных ниже проблем.
- Неизвестность. При отладке или устранении неисправностей, а также при поиске причины ошибки или изменений данных внутри сложных систем, триггеры слишком удобны для разработчиков (и даже администраторов), чтобы забывать о них. Представим себе ситуацию: разработчики знают, что определенной части приложения или кода поручена обработка, скажем, вставка в конкретной таблице, и продолжают сталкиваться с некоторыми конечными значениями, отличными от тех, что были при отправке. Обычно в таких случаях предполагают, что есть некая ошибка в коде, — пока не вспомнят, что триггер, развернутый когда-то в прошлом, просто молчаливо наблюдает за всеми INSERT и изредка управляет ими, выравнивая данные по бизнес-правилам, которые уже не актуальны. Эта же проблема с триггерами типа «развернуть и забыть» может, в свою очередь, доставить немало хлопот при устранении неполадок.
- Сложности. Помимо того, что существуют веские основания избегать включения бизнес-логики в базу данных, факт остается фактом: триггеры на самом деле сложнее, чем кажется. Например, одна из распространенных проблем с триггерами заключается в том, что разработчики, их создающие, мыслят в рамках одной строки, модифицируемой в триггере. Также нередко разработчики ошибочно создают триггеры с неверной скалярной семантикой, что вызывает проблемы, когда оператор вроде UPDATE затрагивает несколько строк. Еще один вопрос, должен ли триггер DML быть запущен до, после или вместо DML — в результате труднее определить, что происходит, когда есть триггеры.
- Производительность и масштабируемость. Триггеры, по определению, выполняются всякий раз, когда работает операция DML. С помощью логики и дополнительной фильтрации определяется, должен ли триггер игнорировать, блокировать или модифицировать DML при попытках изменения. Кроме того, свойства DELETED и INSERTED для псевдотаблиц работают, и нередко можно наблюдать операции с триггерами, требующими более гранулированных и дорогих блокировок, чем обычно. Другими словами, таблица без триггеров всегда будет вести себя лучше, чем таблица с триггерами — это ключевой фактор, когда речь идет о масштабируемости. Хотя триггер может прекрасно работать на небольших базах данных, по мере того, как количество данных и таблиц увеличивается, триггеры выполняют все больше блокировок, повышают нагрузку, снижают производительность и вызывают проблемы с масштабируемостью. Действительно, в некоторых самых сложных случаях, с которыми я когда-либо сталкивался, были со всей тщательностью устроенные триггеры, которые только усугубляли ситуацию.
Когда использовать триггеры
Учитывая, что триггеры сложны и велика вероятность потерпеть неудачу при устранении неполадок и столкнуться с серьезными проблемами в крупных параллельных системах, возникает вопрос: какова их роль в системе? Лично я не сторонник их применения для чего бы то ни было. По большому счету, основная часть логики, которую они реализуют, может быть перемещена в хранимые процедуры или в любой код, уже служащий для модифицирования данных. На мой взгляд, использование триггеров сигнализирует о системной или архитектурной проблеме, поскольку разработчики зачастую начинают применять триггеры в качестве «горячей клавиши», чтобы обеспечить выполнение бизнес-правил с использованием таблицы вместо модифицирующего кода, «захватывающей» всю логику и все функции. В некотором отношении это равносильно попытке написать пре- и пост-фильтры к веб-запросам для вставок или изменений верхних и нижних колонтитулов HTML, вместо того чтобы найти код создания этих объектов и модифицировать его, если необходимо.
Если отвлечься от перечисленных соображений, можно найти одно место, где использование триггеров имеет смысл: это аудит. Аудит необходим для соблюдения законодательных требований, которые обычно слишком обширны для триггеров и ими, как правило, должны заниматься дорогостоящие решения аудита сторонних фирм. Однако для простых сценариев, где вам нужно следить за изменением данных в определенных строках, можно настроить простые триггеры, которые будут захватывать и направлять строки INSERTED, DELETED и UPDATED в таблицу TableName_Audit. Это позволит отслеживать значения и метаданные о том, кто и когда выполнил операцию, без обременительных расходов. Это вовсе не означает, что использование триггеров избавит вас от всех проблем, но, на мой взгляд, это единственная область, где они сыграют свою роль. Если вы все же захотите использовать триггеры, поймите, что операции INSERT, UPDATE, а также DELETE и MERGE изменят единовременно более чем одну строку. Представьте, что ваши аудиторские таблицы будут становиться все больше, а времени на их заполнение строками в конечном итоге потребуется слишком много, если индексация этих таблиц аудита должным образом не оптимизирована для операторов INSERT. В целом, я настоятельно рекомендую не использовать триггеры, учитывая проблемы, которые просто неизбежны.