【设计】MySQL + C# 并发分批查询 DataTable Merge 偶发报错分析及解决方案

在实际的业务场景中,文章 【设计】分批查询数据通用方法(基于接口 + 泛型 + 定点复制) 的方法会遇到偶发报错,下面详细讲解这个报错问题。

一、问题背景

在 .NET 项目中,有一个场景需要对大量数据进行分批查询并行处理,然后将每批次的查询结果合并到一个 DataTable。使用 iBatis.Net 映射 SQL 并调用 QueryForDataTable 方法,原始逻辑如下:

  • 数据量大,分批处理以提高性能

  • 并行调用 AsParallel(),每个批次调用相同查询方法

  • 最终使用 DataTable.Merge 合并结果

问题出现:

  • 偶尔出现 System.Data.DataException: <target> 和 <source> 的属性冲突: DataType 属性不匹配

  • 报错概率不高,偶发,且不同用户或不同机器可能结果不同

  • SQL 语句本身、查询条件相同,顺序不固定


二、问题描述

现象分析:

  1. 分批数据量不同或顺序不同,但总量一致

  2. Merge 报错提示列类型不一致

  3. 排查 SQL 数据发现,首行值不为空

  4. 错误偶发,不是每次都发生

三、问题点分析

1. DataTable.Merge 报错根本原因

  • DataAdapter.Fill 会根据首行推断 DataTable 列类型

  • 并发情况下,多线程 共用同一个 IDbConnection

    • Fill 内部 IDataReader 状态被干扰

    • 首行类型推断可能偶发不一致

  • Merge 时列类型不一致 → 报错 DataType 属性不匹配

2. 为什么偶发

  • 不同线程调度顺序不同 → 某些批次先 Fill 完首行

  • 某些用户一直报错,而其他用户不报 → 多线程共享同一连接的偶发性

  • 数据本身没有问题,SQL 没有问题 → 问题在于 连接共享 + Fill 并发

3. 判断连接是否共享

原代码判断:

cs 复制代码
if (!sqlMap.IsSessionStarted)
{
   sqlMap.OpenConnection(); // 只在第一次打开
}
  • 多线程同时执行时,只有第一个线程会调用 OpenConnection

  • 其他线程共享同一个连接 → 并发不安全

四、解决方案

方案 A:每批次独立连接(推荐)

  • 每批次创建新的 iBatis Session 或独立 IDbConnection

  • 依然使用连接池(Pooling=True)保证性能

  • 优势

    • 并发安全

    • 连接池复用连接,性能几乎无损

  • 注意

    • SqlMap.CreateSession() 或 IDbConnection.Open() 要启用 Pooling

方案 B:SQL 显式 CAST + DataTable Clone

  • 避免 Fill 首行类型推断导致 Merge 报错

  • 在 SQL 层 CAST 关键列类型,例如:

sql 复制代码
CAST(@rowNO AS SIGNED) AS rowNumber,
CAST(IFNULL(weight_col1, weight_col2) AS DECIMAL(10,2)) AS weight

或者在 C# 层预定义 DataTable 列类型,并使用 Clone + ImportRow:(这种方式并不能避免错误,如果首次类型错误,比如为空,后面的都会错误,比如后面为int)

cs 复制代码
var schema = temp.Clone(); // 复制列结构和类型
foreach (DataRow row in temp.Rows)
    dt.ImportRow(row);

作用

  • 保证列类型一致 → Merge 安全

  • 结合独立连接 → 根治偶发报错

五、附加说明

  1. 连接池默认行为

    • MySQL Connector/NET 默认 Pooling=true

    • Open() 从池中取,Close() 归还

    • 连接池复用物理连接,但 同一 IDbConnection 不可多线程 Fill

  2. 为什么有人查询一直报错,有人不报

    • 取决于线程调度顺序和首行类型推断

    • 并发共享连接 → 偶发性错误

  3. 性能考虑

    • 每批次独立连接 + 连接池 → 性能影响几乎可以忽略

    • SQL CAST + Clone → 稍微增加少量 CPU 开销,但保证稳定性

六、总结

  • 问题根源:并发分批 Fill 使用同一 IDbConnection → DataTable 首行类型推断偶发不一致 → Merge 报错

  • 根治方法

    1. 每批次独立连接(连接池复用)

    2. SQL CAST 显式类型,或 C# 层 DataTable schema + Clone + ImportRow

  • 推荐实践

    • 大量数据并发查询时,千万不要共享同一 IDbConnection

    • 对可能 NULL 或数字列显式指定类型

    • 保证 DataTable 列类型一致,Merge 安全

相关推荐
剩下了什么7 小时前
MySQL JSON_SET() 函数
数据库·mysql·json
山峰哥7 小时前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
较劲男子汉8 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
java搬砖工-苤-初心不变8 小时前
MySQL 主从复制配置完全指南:从原理到实践
数据库·mysql
bugcome_com9 小时前
零基础入门C#:一篇搞懂核心知识点
c#
WangYaolove13149 小时前
基于python的在线水果销售系统(源码+文档)
python·mysql·django·毕业设计·源码
山岚的运维笔记10 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
roman_日积跬步-终至千里10 小时前
【LangGraph4j】LangGraph4j 核心概念与图编排原理
java·服务器·数据库
汇智信科10 小时前
打破信息孤岛,重构企业效率:汇智信科企业信息系统一体化运营平台
数据库·重构
野犬寒鸦11 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法