SQL 触发器内游标中的服务器错误处理
SQL Server error handling in cursor inside trigger
我是SQL服务器错误处理的新手,我的英语不是很清楚,所以如果有任何误解,我提前道歉。
问题是:我将多条记录插入table。 table 有一个 AFTER INSERT
触发器,它正在用游标在 FETCH WHILE 循环中一条一条地处理记录。如果发生错误,一切都会回滚。因此,如果插入的记录中只有一个错误的字段,我将丢失所有这些字段。而且插入也回滚,所以我找不到错误的记录。所以我需要处理游标内部的错误,只回滚错误的记录。
我用 3 tables 做了一个测试数据库:
tA
VarSmallint smallint
VarTinyint tinyint
String varchar(20)
tB
ID int (PK, identity)
Timestamp datetime (default: getdate())
VarSmallint smallint
VarTinyint tinyint
String varchar(20)
tC
ID int PK
Timestamp datetime
VarTinyint1 tinyint
VarTinyint2 tinyint
String varchar(10)
tA
包含 3 条记录,其中 1 条错误。我将此内容插入 tB
。
tB
有触发器,将记录一条一条地插入到 tC
中。
tC
只有tinyint变量,所以插入大于255的值会有问题。这是测试出错的地方!
我的触发器是:
ALTER TRIGGER [dbo].[trg_tB]
ON [dbo].[tB]
AFTER INSERT
AS
BEGIN
IF @@rowcount = 0
RETURN;
SET NOCOUNT ON;
DECLARE
@ID AS int,
@Timestamp AS datetime,
@VarSmallint AS smallint,
@VarTinyint AS tinyint,
@String AS varchar(20),
DECLARE curNyers CURSOR DYNAMIC
FOR
SELECT
[ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
FROM INSERTED
ORDER BY [ID]
OPEN curNyers
FETCH NEXT FROM curNyers INTO @ID, @Timestamp, @VarSmallint, @VarTinyint, @String
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
BEGIN TRAN
INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
VALUES (@ID, @Timestamp, @VarSmallint, @VarTinyint, @String)
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
INSERT INTO [dbo].[tErrorLog]([ErrorTime], [UserName], [ErrorNumber],
[ErrorSeverity], [ErrorState],
[ErrorProcedure], [ErrorLine],
[ErrorMessage], [RecordID])
VALUES (SYSDATETIME(), SUSER_NAME(), ERROR_NUMBER(),
ERROR_SEVERITY(), ERROR_STATE(),
ERROR_PROCEDURE(), ERROR_LINE(),
ERROR_MESSAGE(), @ID)
END CATCH
FETCH NEXT FROM curNyers INTO @ID, @Timestamp, @VarSmallint, @VarTinyint, @String
END
CLOSE curNyers
DEALLOCATE curNyers
END
如果我插入 2 条正确的记录,其中 1 条错误,一切都在回滚,我得到一个错误:
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
请帮帮我!如何修改这个触发器才能正常工作?
如果我插入错误的记录,我需要:
- tB中所有插入的记录
- tC的所有好记录
- tErrorLog 中记录了错误
谢谢!
您的触发器中有两场重大灾难:
不要在触发器中使用 光标 - 那太可怕了!只要给定的操作发生,触发器就会触发——您几乎无法控制它们触发的时间和次数。因此,为了不过度损害您的系统性能,触发器应该非常小、快速、灵活 - 不要做任何繁重的工作和触发器中的广泛处理。游标除了敏捷和快速之外什么都不是——它是一个资源消耗、处理器消耗、内存泄漏的怪物——只要你能避免那些,而且绝对是在触发器内! (无论如何,99% 的情况下你不需要它们)
您可以将整个逻辑重写到这个单一、快速、基于集合的语句中:
ALTER TRIGGER [dbo].[trg_tB]
ON [dbo].[tB]
AFTER INSERT
AS
BEGIN
INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
SELECT
[ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
FROM
INSERTED
END
切勿在触发器内调用 COMMIT TRAN
。触发器在导致它触发的语句的上下文和事务中执行 - 如果一切正常,只需让触发器完成,然后事务就会正常提交。如果需要中止,请调用 ROLLBACK
。但是 永远不会 在触发器中间调用 COMMIT TRAN
。只是不要.....
我删除了 TRIGGER
,并将其中的代码复制粘贴到 STORED PROCEDURE
。
然后我在 tB 中添加了一行 Status
,并将默认值设置为 0。
1 是 "Record processed OK",2 是 "Record processing fault"。
我用 WHERE Status = 0
填充光标。
在 TRY
部分中,我将状态更新为 1,在 CATCH
部分中,我将 UPDATE
状态更新为 2。
我没有工作,所以我 运行 来自 Windows 调度程序的 SP,带有批处理文件和 SQLCMD
命令。
现在处理的很好,而且第一次很好用。感谢您的帮助!
我是SQL服务器错误处理的新手,我的英语不是很清楚,所以如果有任何误解,我提前道歉。
问题是:我将多条记录插入table。 table 有一个 AFTER INSERT
触发器,它正在用游标在 FETCH WHILE 循环中一条一条地处理记录。如果发生错误,一切都会回滚。因此,如果插入的记录中只有一个错误的字段,我将丢失所有这些字段。而且插入也回滚,所以我找不到错误的记录。所以我需要处理游标内部的错误,只回滚错误的记录。
我用 3 tables 做了一个测试数据库:
tA
VarSmallint smallint
VarTinyint tinyint
String varchar(20)
tB
ID int (PK, identity)
Timestamp datetime (default: getdate())
VarSmallint smallint
VarTinyint tinyint
String varchar(20)
tC
ID int PK
Timestamp datetime
VarTinyint1 tinyint
VarTinyint2 tinyint
String varchar(10)
tA
包含 3 条记录,其中 1 条错误。我将此内容插入 tB
。
tB
有触发器,将记录一条一条地插入到 tC
中。
tC
只有tinyint变量,所以插入大于255的值会有问题。这是测试出错的地方!
我的触发器是:
ALTER TRIGGER [dbo].[trg_tB]
ON [dbo].[tB]
AFTER INSERT
AS
BEGIN
IF @@rowcount = 0
RETURN;
SET NOCOUNT ON;
DECLARE
@ID AS int,
@Timestamp AS datetime,
@VarSmallint AS smallint,
@VarTinyint AS tinyint,
@String AS varchar(20),
DECLARE curNyers CURSOR DYNAMIC
FOR
SELECT
[ID], [Timestamp], [VarSmallint], [VarTinyint], [String]
FROM INSERTED
ORDER BY [ID]
OPEN curNyers
FETCH NEXT FROM curNyers INTO @ID, @Timestamp, @VarSmallint, @VarTinyint, @String
WHILE @@FETCH_STATUS = 0
BEGIN
BEGIN TRY
BEGIN TRAN
INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String])
VALUES (@ID, @Timestamp, @VarSmallint, @VarTinyint, @String)
COMMIT TRAN
END TRY
BEGIN CATCH
ROLLBACK TRAN
INSERT INTO [dbo].[tErrorLog]([ErrorTime], [UserName], [ErrorNumber],
[ErrorSeverity], [ErrorState],
[ErrorProcedure], [ErrorLine],
[ErrorMessage], [RecordID])
VALUES (SYSDATETIME(), SUSER_NAME(), ERROR_NUMBER(),
ERROR_SEVERITY(), ERROR_STATE(),
ERROR_PROCEDURE(), ERROR_LINE(),
ERROR_MESSAGE(), @ID)
END CATCH
FETCH NEXT FROM curNyers INTO @ID, @Timestamp, @VarSmallint, @VarTinyint, @String
END
CLOSE curNyers
DEALLOCATE curNyers
END
如果我插入 2 条正确的记录,其中 1 条错误,一切都在回滚,我得到一个错误:
Msg 3609, Level 16, State 1, Line 1
The transaction ended in the trigger. The batch has been aborted.
请帮帮我!如何修改这个触发器才能正常工作?
如果我插入错误的记录,我需要:
- tB中所有插入的记录
- tC的所有好记录
- tErrorLog 中记录了错误
谢谢!
您的触发器中有两场重大灾难:
不要在触发器中使用 光标 - 那太可怕了!只要给定的操作发生,触发器就会触发——您几乎无法控制它们触发的时间和次数。因此,为了不过度损害您的系统性能,触发器应该非常小、快速、灵活 - 不要做任何繁重的工作和触发器中的广泛处理。游标除了敏捷和快速之外什么都不是——它是一个资源消耗、处理器消耗、内存泄漏的怪物——只要你能避免那些,而且绝对是在触发器内! (无论如何,99% 的情况下你不需要它们)
您可以将整个逻辑重写到这个单一、快速、基于集合的语句中:
ALTER TRIGGER [dbo].[trg_tB] ON [dbo].[tB] AFTER INSERT AS BEGIN INSERT INTO [dbo].[tC]([ID], [Timestamp], [VarTinyint1], [VarTinyint2], [String]) SELECT [ID], [Timestamp], [VarSmallint], [VarTinyint], [String] FROM INSERTED END
切勿在触发器内调用
COMMIT TRAN
。触发器在导致它触发的语句的上下文和事务中执行 - 如果一切正常,只需让触发器完成,然后事务就会正常提交。如果需要中止,请调用ROLLBACK
。但是 永远不会 在触发器中间调用COMMIT TRAN
。只是不要.....
我删除了 TRIGGER
,并将其中的代码复制粘贴到 STORED PROCEDURE
。
然后我在 tB 中添加了一行 Status
,并将默认值设置为 0。
1 是 "Record processed OK",2 是 "Record processing fault"。
我用 WHERE Status = 0
填充光标。
在 TRY
部分中,我将状态更新为 1,在 CATCH
部分中,我将 UPDATE
状态更新为 2。
我没有工作,所以我 运行 来自 Windows 调度程序的 SP,带有批处理文件和 SQLCMD
命令。
现在处理的很好,而且第一次很好用。感谢您的帮助!