PostgreSQL12搭配EF Core批量更新数据出现Duplicate key value错误如何解决
问题根因
你遇到的23505错误是PostgreSQL的主键唯一约束冲突,说明代码尝试插入重复的FileId值,核心原因是你的「存在则更新、不存在则新增」的判断逻辑在批量/重复数据场景下失效了,具体代码问题如下:
- 输入集合未提前去重:如果传入的
files中存在多个相同FileId的条目,判断逻辑可能重复触发新增操作 - 未检查EF Core本地跟踪缓存:你当前的查询只查数据库,如果上下文已经跟踪了同
FileId的新增实体但还未提交,再次查询数据库会返回空,导致重复Add - 同步查询在异步方法中使用:你用了同步的
FirstOrDefault,不仅会阻塞线程,极端情况下也可能引发时序问题 - 每次循环都提交事务:批量处理时每次调用
SaveChangesAsync会产生大量数据库交互,性能差且增大冲突概率 - 代码层面的判断无法应对并发场景:如果有多个线程/进程同时处理相同
FileId的数据,两边同时查询都判定不存在,同时插入就会触发冲突
修复方案
方案1:优化现有判断逻辑(适合小数据量场景)
- 先对输入的文件集合去重,避免重复处理相同
FileId
private async Task ConvertAndSaveFiles(IDriveItemDeltaCollectionPage files) { // 先按FileId去重,保留最新的条目 var distinctFiles = files.GroupBy(f => f.Id).Select(g => g.Last()).ToList(); foreach (var file in distinctFiles) { var fileInfo = new FileInfo { FileId = file.Id, FileName = file.Name, LastModifiedDateTime = file.LastModifiedDateTime, Path = file.ParentReference.Path }; await SaveFileInfos(fileInfo); } // 批量提交放到循环外,大幅提升性能 await _context.SaveChangesAsync(); }
- 优化
SaveFileInfos的查询逻辑,先查本地缓存再查数据库,改成异步查询
private async Task SaveFileInfos(FileInfo fileInfo) { // 先查EF Core本地跟踪的实体,避免未提交的新增实体漏判 var foundFileInfo = _context.FileInfos.Local.FirstOrDefault(f => f.FileId == fileInfo.FileId) ?? await _context.FileInfos.FirstOrDefaultAsync(f => f.FileId == fileInfo.FileId); if (foundFileInfo == null) { await _context.FileInfos.AddAsync(fileInfo); } else { foundFileInfo.FileName = fileInfo.FileName; foundFileInfo.LastModifiedDateTime = fileInfo.LastModifiedDateTime; foundFileInfo.Path = fileInfo.Path; } // 已经把SaveChanges放到循环外,这里删掉原有的提交逻辑 }
方案2:使用PostgreSQL原生Upsert(推荐,适合批量/高并发场景)
直接用PostgreSQL的INSERT ON CONFLICT语法,在数据库层面处理冲突,比代码层面判断更可靠,性能也更高:
private async Task SaveFileInfos(FileInfo fileInfo) { await _context.Database.ExecuteSqlInterpolatedAsync(@$" INSERT INTO ""FileInfos"" (""FileId"", ""FileName"", ""LastModifiedDateTime"", ""Path"") VALUES ({fileInfo.FileId}, {fileInfo.FileName}, {fileInfo.LastModifiedDateTime}, {fileInfo.Path}) ON CONFLICT (""FileId"") DO UPDATE SET ""FileName"" = EXCLUDED.""FileName"", ""LastModifiedDateTime"" = EXCLUDED.""LastModifiedDateTime"", ""Path"" = EXCLUDED.""Path""; "); }
额外优化建议
如果单批次处理数据量超过1000条,建议每处理500-1000条就清空一次EF Core的跟踪缓存,避免内存占用过高:
_context.ChangeTracker.Clear();
内容的提问来源于stack exchange,提问作者ChsharpNewbie
相关产品推荐
相关产品推荐

