You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hangfire从仪表盘手动触发作业时为何出现PK_HangFire_Hash主键约束冲突?

Hangfire手动触发周期性作业时的唯一约束冲突问题

环境信息

  • 使用的Hangfire包版本:Hangfire.AspNetCore: 1.8.0、Hangfire.Core: 1.8.0、Hangfire.SqlServer: 1.8.0

问题现象

从仪表盘手动触发周期性作业IWorkOrderBackgroundService.WatchInProgressWorkOrders时,偶尔会抛出主键约束冲突异常,提示无法向HangFire.Hash和HangFire.Set表插入重复键。

具体错误信息

Microsoft.Data.SqlClient.SqlException (0x80131904):
违反主键约束'PK_HangFire_Hash'。无法在对象'HangFire.Hash'中插入重复键,重复键值为(recurring-job:IWorkOrderBackgroundService.WatchInProgressWorkOrders, LastExecution)。
违反主键约束'PK_HangFire_Hash'。无法在对象'HangFire.Hash'中插入重复键,重复键值为(recurring-job:IWorkOrderBackgroundService.WatchInProgressWorkOrders, NextExecution)。
违反主键约束'PK_HangFire_Hash'。无法在对象'HangFire.Hash'中插入重复键,重复键值为(recurring-job:IWorkOrderBackgroundService.WatchInProgressWorkOrders, LastJobId)。
违反主键约束'PK_HangFire_Set'。无法在对象'HangFire.Set'中插入重复键,重复键值为(recurring-jobs, IWorkOrderBackgroundService.WatchInProgressWorkOrders)。
语句已终止。语句已终止。语句已终止。语句已终止。
在Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
在Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
在Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
在Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
在Microsoft.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
在Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
在Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method)
在Microsoft.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(TaskCompletionSource`1 completion, Boolean sendToPipe, Int32 timeout, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String methodName)
在Microsoft.Data.SqlClient.SqlCommand.ExecuteNonQuery()
在Microsoft.Data.SqlClient.SqlCommandSet.ExecuteNonQuery()
在Hangfire.SqlServer.SqlCommandBatch.ExecuteNonQuery()
在Hangfire.SqlServer.SqlServerWriteOnlyTransaction.b__17_0(DbConnection connection, DbTransaction transaction)
在Hangfire.SqlServer.SqlServerStorage.<>c__DisplayClass39_0.b__0(DbConnection connection, DbTransaction transaction)
在Hangfire.SqlServer.SqlServerStorage.<>c__DisplayClass40_0`1.<UseTransaction>b__0(DbConnection connection)
在Hangfire.SqlServer.SqlServerStorage.UseConnection[T](DbConnection dedicatedConnection, Func`2 func)
在Hangfire.SqlServer.SqlServerStorage.UseTransaction[T](DbConnection dedicatedConnection, Func`3 func, Nullable`1 isolationLevel)
在Hangfire.SqlServer.SqlServerStorage.UseTransaction(DbConnection dedicatedConnection, Action`2 action)
在Hangfire.SqlServer.SqlServerWriteOnlyTransaction.Commit()
在Hangfire.RecurringJobManager.TriggerJob(String recurringJobId)
在Hangfire.RecurringJobManager.Trigger(String recurringJobId)
在Hangfire.Dashboard.DashboardRoutes.<>c.<.cctor>b__4_27(IRecurringJobManager manager, String jobId)
在Hangfire.Dashboard.RouteCollectionExtensions.<>c__DisplayClass4_0.b__0(DashboardContext context, String jobId)
在Hangfire.Dashboard.BatchCommandDispatcher.Dispatch(DashboardContext context)
在Hangfire.Dashboard.AspNetCoreDashboardMiddleware.Invoke(HttpContext httpContext)

根本原因

手动触发周期性作业时,Hangfire会执行一批SQL操作更新该作业的元数据(包括哈希表中的执行记录、集合中的作业标识)。当同一时间存在多个触发请求(比如用户连续点击触发按钮,或后台调度器刚好在更新该作业元数据),就会引发并发写入冲突:第一个事务提交后,第二个事务尝试插入已存在的主键记录,触发SQL唯一约束异常。

解决方案

  • 避免重复触发:提醒用户不要连续点击仪表盘触发按钮,确保同一时间只有一个手动触发请求
  • 升级Hangfire版本:升级到1.8.x系列后续补丁版本,部分版本优化了并发场景下的事务处理逻辑,可减少此类冲突
  • 自定义触发逻辑:若需频繁手动触发,直接使用BackgroundJob.Enqueue<IWorkOrderBackgroundService>(x => x.WatchInProgressWorkOrders())添加一次性作业,绕过RecurringJobManager.Trigger对周期性作业元数据的修改操作
  • 调整事务隔离级别:在Hangfire SQL存储配置中,尝试调整事务隔离级别(例如改为ReadCommitted),但需注意此调整可能影响其他作业性能,需在测试环境验证后再上线

内容的提问来源于stack exchange,提问作者LP13

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 12:36:06