触发器检测存储过程是否调用了DELETE或UPDATE
Trigger to detect whether DELETE or UPDATE is called by stored proc
我有一个场景,某些用户必须有权更新或删除生产中的某些记录。我想做的是采取保护措施,以确保他们不会意外更新或删除 table 的大部分(或全部),而是根据他们的需要只更新或删除几条记录。所以我写了一个简单的触发器来完成这个。
CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS
BEGIN
IF (SELECT COUNT(*) FROM Deleted) > 15
BEGIN
RAISERROR ('Bulk deletes from this table are not allowed', 16, 1)
ROLLBACK
END
END --end trigger
但问题来了。有一个存储过程可以对 table 进行批量更新。用户可以而且应该被允许调用存储过程,因为它的范围更受限制。所以不幸的是,我的触发器会阻止他们在需要时调用存储过程。
我想到的唯一解决方案是 运行 将存储过程作为模拟用户,然后修改触发器以将该用户排除在逻辑之外。但这会在我的环境中带来其他问题。不是无法克服table,而是很烦人。尽管如此,这似乎是唯一可行的选择。
我的思考方式是否正确,或者是否有更好的方法?
一种方法是:
- 创建一个存储过程来执行所需的工作
- 如果标准差异很大,请为每种“类型”的工作创建多个程序
- 向所需用户授予执行允许他们执行的那些过程(“工作种类”)的权限。 (您正在使用数据库角色和域组,对吗?)
- 撤消对 table
进行临时数据修改的权限
设置起来比较繁琐,但支持“最少权限原则”,允许用户只做他们应该做的事情。
您可以在触发器中添加 @@NESTLEVEL
的检查。临时语句的值为 1,从存储过程调用时为 2。
CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS
BEGIN
IF (SELECT COUNT(*) FROM Deleted) > 15
AND @@NESTLEVEL = 1 --ad-hoc delete
BEGIN
RAISERROR ('Bulk deletes from this table are not allowed', 16, 1);
ROLLBACK;
END;
END;
我通常用 CONTEXT_INFO()
来处理这个问题。这为您提供了比 @@NESTLEVEL
更好的控制,因为您实际上可以识别执行调用的特定存储过程,并在需要时单独处理它们。你这样做如下:
- 将过程名称添加到
CONTEXT_INFO()
例如
-- START OF STORED PROCEDURE
-- Tell the trigger who we are, and that we can be trusted.
declare @OldContext char(128), @NewContext varbinary(128);
-- Get existing context_info()
set @OldContext = coalesce(convert(char(128), context_info()), '');
-- Add new info to context_info
set @NewContext = convert(varbinary(128),convert(char(128), 'dbo.MyProcedureName'));
-- Store new context info
set context_info @NewContext;
-- STORED PROCEDURE CONTENT
-- END OF STORED PROCEDURE
-- Restore context_info
set @NewContext = convert(varbinary(128), @OldContext);
set context_info @NewContext;
- 如果
CONTEXT_INFO()
来自可信来源,则尽早触发 return,例如
-- START OF TRIGGER
declare @NewContext char(128) = coalesce(convert(char(128),context_info()),'');
if @NewContext in ('dbo.MyProcedureName') begin
return;
end;
这种方法的另一个优点(对于其他触发器用途)是您可以避免在从 SP 调用时在触发器中执行逻辑。因为您经常将逻辑放在触发器中,以确保无论 insert/update/delete 如何发生,它都会发生。但是当在 SP 中完成时,您可以确保在 SP 中执行所需的逻辑,从而避免需要在触发器中执行。如果由于触发器中的逻辑太多而导致性能问题,这尤其有用。
注意:对于 SQL Server 2016+,您可以以类似的方式使用 SESSION_CONTEXT()
。
我有一个场景,某些用户必须有权更新或删除生产中的某些记录。我想做的是采取保护措施,以确保他们不会意外更新或删除 table 的大部分(或全部),而是根据他们的需要只更新或删除几条记录。所以我写了一个简单的触发器来完成这个。
CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS
BEGIN
IF (SELECT COUNT(*) FROM Deleted) > 15
BEGIN
RAISERROR ('Bulk deletes from this table are not allowed', 16, 1)
ROLLBACK
END
END --end trigger
但问题来了。有一个存储过程可以对 table 进行批量更新。用户可以而且应该被允许调用存储过程,因为它的范围更受限制。所以不幸的是,我的触发器会阻止他们在需要时调用存储过程。
我想到的唯一解决方案是 运行 将存储过程作为模拟用户,然后修改触发器以将该用户排除在逻辑之外。但这会在我的环境中带来其他问题。不是无法克服table,而是很烦人。尽管如此,这似乎是唯一可行的选择。
我的思考方式是否正确,或者是否有更好的方法?
一种方法是:
- 创建一个存储过程来执行所需的工作
- 如果标准差异很大,请为每种“类型”的工作创建多个程序
- 向所需用户授予执行允许他们执行的那些过程(“工作种类”)的权限。 (您正在使用数据库角色和域组,对吗?)
- 撤消对 table 进行临时数据修改的权限
设置起来比较繁琐,但支持“最少权限原则”,允许用户只做他们应该做的事情。
您可以在触发器中添加 @@NESTLEVEL
的检查。临时语句的值为 1,从存储过程调用时为 2。
CREATE TRIGGER delete_check
ON dbo.My_table
AFTER UPDATE,DELETE AS
BEGIN
IF (SELECT COUNT(*) FROM Deleted) > 15
AND @@NESTLEVEL = 1 --ad-hoc delete
BEGIN
RAISERROR ('Bulk deletes from this table are not allowed', 16, 1);
ROLLBACK;
END;
END;
我通常用 CONTEXT_INFO()
来处理这个问题。这为您提供了比 @@NESTLEVEL
更好的控制,因为您实际上可以识别执行调用的特定存储过程,并在需要时单独处理它们。你这样做如下:
- 将过程名称添加到
CONTEXT_INFO()
例如
-- START OF STORED PROCEDURE
-- Tell the trigger who we are, and that we can be trusted.
declare @OldContext char(128), @NewContext varbinary(128);
-- Get existing context_info()
set @OldContext = coalesce(convert(char(128), context_info()), '');
-- Add new info to context_info
set @NewContext = convert(varbinary(128),convert(char(128), 'dbo.MyProcedureName'));
-- Store new context info
set context_info @NewContext;
-- STORED PROCEDURE CONTENT
-- END OF STORED PROCEDURE
-- Restore context_info
set @NewContext = convert(varbinary(128), @OldContext);
set context_info @NewContext;
- 如果
CONTEXT_INFO()
来自可信来源,则尽早触发 return,例如
-- START OF TRIGGER
declare @NewContext char(128) = coalesce(convert(char(128),context_info()),'');
if @NewContext in ('dbo.MyProcedureName') begin
return;
end;
这种方法的另一个优点(对于其他触发器用途)是您可以避免在从 SP 调用时在触发器中执行逻辑。因为您经常将逻辑放在触发器中,以确保无论 insert/update/delete 如何发生,它都会发生。但是当在 SP 中完成时,您可以确保在 SP 中执行所需的逻辑,从而避免需要在触发器中执行。如果由于触发器中的逻辑太多而导致性能问题,这尤其有用。
注意:对于 SQL Server 2016+,您可以以类似的方式使用 SESSION_CONTEXT()
。