我们目前有一个基于触发器的系统,它可以执行以下操作:
- 当某些表中的记录被修改时,触发器会触发,使另一个表中不同数量的记录中的字段为 NULL,我们将称该表为 [LRPP]。
- [LRPP] 上的触发器随后会触发一个过程,该过程根据哪些记录被标记为 NULL 来执行一些大型计算。此调用是在使用 CLR 的单独线程中进行的,因此在执行后,当前事务将继续进行,而无需等待它完成。
- 当计算完成时(在他们单独的线程中),他们在 [LRPP] 上进行插入和/或更新和/或删除
问题在于,如果此进程针对同一组记录同时触发多次,由于高度相关的数据被更改,它几乎总是会死锁。死锁发生在最初用 NULL 值更新 [LRPP] 的触发器部分与运行计算的过程执行的 DELETE 操作之间。
我可以做些什么来解决这个问题?我正在考虑将 INSERT/UPDATE/DELETE 部分包装在一个显式事务中,但我不确定这是否有帮助。
我有以下限制:
- 目前无法更改整体架构,必须使用具有 NULL 值的基于触发器的系统。
- 当前正在发生的所有操作必须继续实时发生,这个过程中的任何一个都不能以任何方式延迟或聚合。
我最初的想法是,我们需要用某种定期截断的跟踪表来替换“NULL 设置”部分,但这是一个需要时间的重大更改。在设计和测试更大的修复程序之前,我正在寻找更快的方法来缓解这个问题。
谢谢!
只要代码路径非常可预测,一个快速的解决方案就是使用
sp_getapplock
请求独占锁。此方法不会对表本身进行锁定——应用程序(数据库)代码是否遵守该锁定。这将做的是序列化运行异步进程,以便一次只有一个针对数据运行。这意味着你可能仍然有死锁,但它会消除进程的多个实例之间的死锁。
您需要注意正确平衡进程数和执行时间,否则您最终可能会创建一个永远无法完全清除的队列。由于它现在在 CLR 中实现的方式,这很可能最终导致 SQL Server 线程匮乏,这非常糟糕。请注意,无论如何,现在肯定有这种可能性。
从长远来看,您可能希望将此解决方案切换为使用 Service Broker,它在数据库本身中为您提供消息传递和排队功能。如果您是新手,我这里有一段视频可以解释 Service Broker 的基础知识。