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

相关推荐
panzer_maus2 小时前
Redis的简单介绍(1)
数据库·redis·缓存
我科绝伦(Huanhuan Zhou)2 小时前
DM数据库逻辑存储结构解析
数据库·oracle
Dreamcatcher_AC2 小时前
Node.js留言板开发全流程解析
前端·javascript·mysql·node.js·express
天空属于哈夫克32 小时前
企业微信 API 开发:外部群自动化推送的技术实现
数据库·microsoft
我是唐青枫2 小时前
深入理解 C#.NET IEnumerable<T>:一切集合的起点
c#·.net
脑子慢且灵2 小时前
写项目之数据库使用有感
数据库·er图
倒流时光三十年2 小时前
PostgreSQL 高级特性. FILTER RETURNING 特性
数据库·postgresql·filter·sql调优
填满你的记忆2 小时前
【从零开始——Redis 进化日志|Day1】初见 Redis,开启内存加速之旅
数据库·redis·缓存
么么...2 小时前
掌握 MySQL:约束、范式与视图详解
数据库·经验分享·sql·mysql