ddl触发器必须显式包含'drop_index'事件类型,用eventdata()解析objectname和targetobjectname提取索引名与表名,raiserror需≥16级才能阻断操作并回滚事务。

DDL触发器必须覆盖DROP_INDEX事件
SQL Server 的 DDL 触发器不会自动拦截 DROP INDEX,它和 DROP_TABLE 是独立事件类型。漏掉这个,就等于放行了最常被误操作的索引清理行为。
常见错误是只写 WHERE @EventType IN ('DROP_TABLE', 'ALTER_TABLE'),结果 DROP INDEX IX_Orders_CustomerId 照常执行。
- 必须显式加入
'DROP_INDEX':WHERE @EventType IN ('DROP_TABLE', 'ALTER_TABLE', 'TRUNCATE_TABLE', 'DROP_INDEX') -
DROP_INDEX事件在 SQL Server 2005+ 全版本中稳定存在,无需版本判断 - 注意大小写不敏感,但字符串字面量建议全大写以匹配
EVENTDATA()返回值
用EVENTDATA()安全提取索引名和所属表
OBJECT_NAME() 在 DROP INDEX 触发时返回 NULL——因为索引已删或尚未解析完成。唯一可靠方式是解析 XML:
正确写法:CAST(EVENTDATA() AS XML).value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname') 提取索引名;.value('(/EVENT_INSTANCE/TargetObjectName)[1]', 'sysname') 提取被删索引所在的表名(不是 ObjectName)。
-
TargetObjectName字段才表示索引依附的表,比如Orders -
ObjectName是索引自身名称,如IX_Orders_Status - 必须用
TRY...CATCH包裹整个解析过程,防止某次 EVENTDATA() 结构微调(如节点重命名)导致触发器崩溃、阻塞所有 DDL
RAISERROR 必须 ≥16 级且带事务回滚语义
级别 10–15 的 RAISERROR 只输出消息,语句继续执行;只有 ≥16 才能中断当前批处理并回滚事务。
示例阻断逻辑:
IF @EventType = 'DROP_INDEX' AND @TargetObjectName NOT IN ('Config', 'Lookup')
BEGIN
RAISERROR('DROP INDEX blocked on table %s', 16, 1, @TargetObjectName);
RETURN;
END
- 别用
PRINT或低级别RAISERROR,它们无法阻止删除发生 - 不要依赖
ROLLBACK显式回滚——DDL 本身就在隐式事务中,RAISERROR≥16 会自动触发回滚 - 若需放行特定场景(如部署脚本),建议结合
PROGRAM_NAME()或CONTEXT_INFO()判断,而不是模糊匹配表名
触发器启用状态和权限常被忽略
新建的 DDL 触发器默认 is_disabled = 1,不手动启用等于没写。
检查命令:SELECT is_disabled FROM sys.triggers WHERE name = 'tr_block_drop_index';启用命令:ENABLE TRIGGER tr_block_drop_index ON DATABASE。
- 权限要求:创建者需有
ALTER ANY DATABASE DDL TRIGGER权限;运行时需能读取sys.dm_exec_sessions(如果要记录操作人) - 别在
master库建数据库级触发器来防用户库索引删除——作用域不匹配,完全无效 - 测试时务必用普通账号执行
DROP INDEX,避免用 sa 账号绕过权限校验造成“假成功”










