在实际的业务场景中,文章 【设计】分批查询数据通用方法(基于接口 + 泛型 + 定点复制) 的方法会遇到偶发报错,下面详细讲解这个报错问题。
一、问题背景
在 .NET 项目中,有一个场景需要对大量数据进行分批查询并行处理,然后将每批次的查询结果合并到一个 DataTable。使用 iBatis.Net 映射 SQL 并调用 QueryForDataTable 方法,原始逻辑如下:
-
数据量大,分批处理以提高性能
-
并行调用
AsParallel(),每个批次调用相同查询方法 -
最终使用
DataTable.Merge合并结果
问题出现:
-
偶尔出现
System.Data.DataException: <target> 和 <source> 的属性冲突: DataType 属性不匹配 -
报错概率不高,偶发,且不同用户或不同机器可能结果不同
-
SQL 语句本身、查询条件相同,顺序不固定
二、问题描述
现象分析:
-
分批数据量不同或顺序不同,但总量一致
-
Merge 报错提示列类型不一致
-
排查 SQL 数据发现,首行值不为空
-
错误偶发,不是每次都发生
三、问题点分析
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 安全
-
结合独立连接 → 根治偶发报错
五、附加说明
-
连接池默认行为
-
MySQL Connector/NET 默认 Pooling=true
-
Open() 从池中取,Close() 归还
-
连接池复用物理连接,但 同一 IDbConnection 不可多线程 Fill
-
-
为什么有人查询一直报错,有人不报
-
取决于线程调度顺序和首行类型推断
-
并发共享连接 → 偶发性错误
-
-
性能考虑
-
每批次独立连接 + 连接池 → 性能影响几乎可以忽略
-
SQL CAST + Clone → 稍微增加少量 CPU 开销,但保证稳定性
-
六、总结
-
问题根源:并发分批 Fill 使用同一 IDbConnection → DataTable 首行类型推断偶发不一致 → Merge 报错
-
根治方法:
-
每批次独立连接(连接池复用)
-
SQL CAST 显式类型,或 C# 层 DataTable schema + Clone + ImportRow
-
-
推荐实践:
-
大量数据并发查询时,千万不要共享同一 IDbConnection
-
对可能 NULL 或数字列显式指定类型
-
保证 DataTable 列类型一致,Merge 安全
-