【设计】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 安全

相关推荐
一江寒逸1 分钟前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain3 分钟前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希40 分钟前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
我是唐青枫41 分钟前
C#.NET gRPC 深入解析:Proto 定义、流式调用与服务间通信取舍
开发语言·c#·.net
荒川之神1 小时前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员1 小时前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java1 小时前
Redis简单应用
数据库·spring boot·tomcat·maven
unicrom_深圳市由你创科技1 小时前
做虚拟示波器这种实时波形显示的上位机,用什么语言?
c++·python·c#
一个天蝎座 白勺 程序猿1 小时前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb
不知名的老吴1 小时前
Redis的延迟瓶颈:TCP栈开销无法避免
数据库·redis·缓存