无并发场景下EF更新Transmit、Messages表随机报更新行数0异常
问题场景与故障现象
两张表为一对多关联关系,EDMX模型已正确识别关联,表结构如下:
(1) (N) Transmit -------------< Messages --------------- ----------- TransmitId (PK) TransmitId (PK) InternalId SendingDate (PK) LastStatus Content MessageType Answer Status
核心业务逻辑
代码运行在传入预先获取的transmitId的子过程中,实现对WebService调用结果的持久化,完整代码如下:
Try Using db As New MyDBEntities ' 调用WebService发送消息 FullResp = api.MessagesPostWithHttpInfo(td) ' 更新Transmit表对应记录 Dim t As Transmit = db.Transmit.Find(transmitId) t.InternalId = FullResp.Data.id t.LastStatus = If(FullResp.StatusCode = System.Net.HttpStatusCode.OK, Pionira.Status.Success, Pionira.Status.Failure) ' 更新关联的最新一条Messages记录 With t.Message.OrderByDescending(Function(j) j.SendingDate).First() ' 取当前Transmit关联的最新Message .Answer = ApiClient.ReadAsBytes(ApiClient.Serialize(FullResp)) .Status = t.LastStatus End With ' 提交数据库变更 db.SaveChanges() End Using Catch ex As Exception Return System.Net.HttpStatusCode.NotAcceptable End Try
异常表现
该逻辑累计调用77次,约2/3概率抛出固定异常:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
运行环境为本地电脑的本地数据库,无其他进程对目标表行进行并发修改,不存在并发访问场景。故障发生后,Messages表的部分数据如下(约定Status=2代表更新成功,Status=0代表更新失败):
TransmitId SendingDate Content Answer Status 1084 2021-03-18 16:05:36.547 [BLOB] 0 1085 2021-03-18 16:05:37.583 [BLOB] 0 1086 2021-03-18 16:05:38.630 [BLOB] [BLOB] 2 1087 2021-03-18 16:05:39.687 [BLOB] 0 1088 2021-03-18 16:05:40.787 [BLOB] 0 1089 2021-03-18 16:05:42.100 [BLOB] [BLOB] 2
多次重试过程中,每次成功更新的行不固定,77次调用通常仅22-29条记录完成更新,从未出现全部更新成功的情况。
故障根本原因
该问题和并发场景无任何关联,核心触发原因是Messages表联合主键的字段类型精度映射不匹配,触发了EF内置的乐观并发校验逻辑:
- EF会自动将所有主键字段纳入UPDATE语句的WHERE匹配条件,即使未显式配置并发令牌,只要实体主键值和数据库实际存储值存在偏差,就会判定为更新异常。
- 从样本数据的时间戳格式判断,
SendingDate对应的数据库字段大概率是SQL Server原生datetime类型,该类型的实际存储精度为3.33毫秒,并非严格意义上的毫秒精度;而.NET端DateTime类型精度为100纳秒,EF从数据库读取时间值映射为.NET实体时,会按照自身精度规则补全时间的小数位。 - 执行UPDATE时,EF会使用实体上存储的、带补全精度的
SendingDate值做WHERE匹配,但数据库实际存储的原始时间值因精度截断,和.NET实体上的值并不完全相等,最终导致UPDATE语句匹配到0行,抛出“影响行数异常”的并发类报错。 - 故障呈随机分布的原因:不同时间戳与3.33毫秒精度网格的对齐关系是随机的——部分时间戳读取后的值回写时刚好能匹配数据库原值,就能更新成功;其余存在精度偏差的就会匹配失败。这完全匹配观察到的约1/3成功率、成功行随机分布的现象。
内容的提问来源于stack exchange,提问作者Skippy
相关产品推荐
相关产品推荐

