Делаем вставку без отката
Вот странный вопрос: есть ли способ сделать вставку без отката в таблицу?
Вот сценарий: у нас есть триггер, который делает вещи.
Иногда этот триггер будет вызывать RAISERRROR()
, И внешняя транзакция откатывается.
Однако в триггере я хотел бы вставить значения в таблицу журналов и не допустить, чтобы они исчезали во время отката, вставка без транзакции, если хотите.
3 ответа
улов банана
Вы можете ловить исключения с try-catch
и передать / выбросить их во внешнюю область видимости, где должны существовать такие же вещи. Это, кстати, позволяет собирать call-stack
, Это решение может быть реализовано, если вы работаете только через сохраненные программы. Каждый процесс должен иметь такой шаблон:
begin try
end try
begin catch
if @@trancount > 0
rollback
insert into <log> (...)
values (...)
throw
end catch
Таким образом, самая верхняя процедура успешно вставит строку в таблицу журнала.
Минусы:
- с глубокой иерархией - слишком много "фиктивных" вставок для отката
- все еще можно ничего не регистрировать, если:
- тяжесть слишком высока
- ИП не соответствует этому образцу
- есть внешнее / клиентское управление транзакциями
Плюсы:
- Не идеально, но выполнимо и не слишком сложно для реализации.
Обратная связь:
- Я использовал этот подход на производственном сервере небольшого проекта, собирал стек вызовов и так далее. Это было бэк-офисное программное обеспечение с ~50-100 онлайн-пользователями, с некоторым количеством работающих роботов, далеко не "высоконагруженным". Работало нормально, помогло исправить многие проблемы. Накладные расходы никого не беспокоили.
неуловимый бандит
Вы можете создать CLR-сборку, которая будет вставлена в таблицу журнала и ничего более... Но! Можно указать отдельное соединение, чтобы эта сборка работала с БД через это отдельное соединение. Что означает - в отдельной области. Таким образом, метод этой сборки вызывается из транзакции, однако выполняется независимо от этой области транзакции.
Итак, вместо:
using(SqlConnection connection = new SqlConnection("context connection=true"))
просто укажите обычную строку подключения. После вызова - выбросить исключение с измененным ERROR_STATUS
чтобы избежать дополнительной регистрации той же ошибки.
begin try
end try
begin catch
if @@trancount > 0
rollback
if @@ERRROR_STATUS != @done_with_logging
exec asm.log(...)
raiserror @err_msg, @severity, @done_with_logging
end catch
Минусы:
- может привести к проблемам в пуле соединений
- немного длинный способ получить тривиальную вставку
- может вызвать дополнительные проблемы, связанные с установлением соединения и самой сборки (+ разрешения, владение и т. д.)
Плюсы:
- идея немного волнующая
- может войти в файл вместо БД
Обратная связь:
- Я не могу вспомнить, использовал ли я этот подход на prod (хотя я вспоминаю это как более недавний опыт... или просто как попытку реализовать это в целях тестирования)
тук, тук, админ
Это простое утверждение (на самом деле просто дополнительная опция WITH LOG
) запишет любое сообщение об ошибке в журнал событий SQL SERVER:
RAISERROR(...) WITH LOG
Это не тот способ, которым должен использоваться журнал SQL SERVER, но это самый быстрый способ записать что-то важное (для решения проблемы). Зарегистрированные события можно посмотреть в окнах агента SSMS.
Минусы:
- могут быть соображения разрешений
- плохой поиск и нулевые возможности настройки
- dba будет тебя ненавидеть (если он, конечно, будет проверять логи сервера)
Плюсы:
- однострочная реализация
Обратная связь:
- Я сталкивался с таким способом регистрации только в одной компании. Я предполагаю, что некоторое время назад он использовался несколькими разработчиками для обнаружения незначительных ошибок, но позже получил распространение по всему коду и стал "стандартом". Итак, через некоторое время стало почти невозможно найти что-то конкретное в журнале событий... и фактически лишь немногие коллеги имели доступ к журналу сервера Prod. Так что буквально а) вредно б) бесполезно. Так что я избавился от этого из управляемой части системы, как только нашел время для.
летающий голландец
{место для разговоров на основе мнения о построении системы на основе триггеров DML}
Мне кажется, что вы не используете хранимые процедуры в своем проекте и вместо этого выполняете специальные запросы. Если у вас есть бэкэнд-приложение с ORM или чем-то в этом роде - пишите в него логи. Более того, возможно, это внутреннее приложение - лучшее место для того, чтобы делать то, что вы делаете внутри этого триггера.
Если ваш проект представляет собой клиент-серверное приложение без приложения appserver / backend, и все, что вы получили, - это специальный запрос и триггер, то для регистрации не так много данных. Нет стека вызовов (на стороне сервера). И будет трудно определить, как вы (пользователь, приложение) пришли к этому конкретному исключению. Таким образом, регистрация на стороне клиента может быть более полезной в этом случае.
Если вы создадите табличную переменную, а затем вставите ее в нее - она не будет включена в откат, поэтому вы можете затем сбросить содержимое в постоянную таблицу.
Например
declare @tab table (msg varchar(255))
BEGIN TRY
BEGIN TRANSACTION
select 1+2
INSERT @tab values ('first step complete')
SElect 1/0
INSERT @tab values ('2nd step complete')
COMMIT
END TRY
BEGIN CATCH
ROLLBACK
SELECT * FROM @tab
END CATCH
Пишу это через некоторое время. Вы можете использовать вместо триггера:
create table t
(
i int,
s varchar(10)
)
go
create table t2
(
i int,
s varchar(10)
)
go
create table tlog
(
i int,
s varchar(10)
)
go
alter trigger tt on t
INSTEAD OF INSERT
AS
BEGIN
rollback transaction
raiserror ('something went wrong', 16, 2)
insert tlog (i,s)
select i, s
from inserted
END
go
truncate table t
truncate table t2
truncate table tlog
go
select * from t
select * from t2
select * from tlog
go
begin transaction
insert t2 (i,s) values (1, 'abc')
insert t (i,s) values (1, 'abc')
commit transaction
go
select * from t
select * from t2
select * from tlog
Получил следующий вывод:
является
(Затронуты 0 строк)
является
(Затронуты 0 строк)
является
(Затронуты 0 строк)
(Затронуто 1 строка (строки)) Сообщение 50000, уровень 16, состояние 2, процедура tt, строка 7 что-то пошло не так
(Затронуто 1 строка (строки)) Сообщение 3609, уровень 16, состояние 1, строка 4 Транзакция завершилась срабатыванием триггера. Пакет был прерван. является
(Затронуты 0 строк)
является
(Затронуты 0 строк)
является
1 abc
(Затронута 1 строка (и))