.NET 6+ 项目应优先选用开源库 MySqlConnector 而非 Oracle 的 MySql.Data,因其原生支持 async/await、无连接泄漏与死锁问题、无 GPL 传染风险,且完全兼容 API;需注意显式 await OpenAsync() 等异步方法,连接字符串中启用 Allow User Variables=true 和 Allow Load Local Infile=true 才能支持用户变量和 MySqlBulkCopy 批量插入。

用 MySqlConnector,不是 MySql.Data
官方 MySql.Data(Oracle 维护)长期存在异步支持弱、连接泄漏、高并发下死锁等问题,.NET 6+ 项目应优先选开源库 MySqlConnector。它完全兼容 MySql.Data 的 API,但重写了底层通信协议,原生支持 async/await,且无 GPL 传染风险。
-
MySqlConnectorNuGet 包名就是MySqlConnector,安装命令:dotnet add package MySqlConnector
- 代码中仍用
using MySqlConnector;,而非MySql.Data.MySqlClient - 连接字符串格式完全一致,无需修改 —— 比如
"Server=localhost;Database=test;Uid=root;Pwd=123;" - 若项目已用
MySql.Data,只需替换 NuGet 包 + 修改 using,其余MySqlConnection、MySqlCommand等类行为不变
MySqlConnection.OpenAsync() 必须显式 await
用 MySqlConnector 时,.Open() 是同步阻塞调用,会拖垮线程池;真正异步的是 .OpenAsync()。不 await 它,连接可能未就绪就执行查询,抛出 InvalidOperationException: Connection must be Open。
- 错误写法:
conn.Open(); // 同步,不推荐
- 正确写法:
await conn.OpenAsync();
- 所有后续操作(
ExecuteReaderAsync、ExecuteScalarAsync、ExecuteNonQueryAsync)也都必须加await - 若在非 async 方法里调用,不要用
.Result或.Wait(),会死锁;应重构为 async 入口
连接字符串里加 Allow User Variables=true 才能用 @ 变量
MySQL 原生不支持 SQL Server 风格的 @param 参数占位符,但 MySqlConnector 默认启用用户变量模拟。如果漏配,执行含 SET @x := ? 或 SELECT @x 的语句会报错 Unknown column '@x' in 'field list'。
- 连接字符串需显式开启:
Server=localhost;Database=test;Uid=root;Pwd=123;Allow User Variables=true;
- 该选项不影响常规参数化查询(
WHERE id = ?),只影响显式使用@开头的用户变量 - 若不用用户变量,可不加;但一旦用了又没开,错误不明显,容易卡在逻辑里查不出原因
批量插入别用循环 INSERT,改用 MySqlBulkCopy
MySqlConnector 自带 MySqlBulkCopy 类,性能比逐条 INSERT 快 10–50 倍。它底层走 LOAD DATA LOCAL INFILE 协议,但封装成 .NET API,无需手动导出 CSV。
- 前提:MySQL 服务端配置允许
local_infile=ON,且连接字符串加Allow User Variables=true;Allow Load Local Infile=true; - 示例:
using var bulk = new MySqlBulkCopy(conn); bulk.DestinationTableName = "users"; bulk.WriteToServer(dataTable); // dataTable 是 DataTable 或 IDataReader
- 注意:目标表字段顺序必须与
DataTable列顺序一致,不按列名匹配 - 不支持自增主键返回值,如需插入后取 ID,仍得用单条
INSERT ... VALUES (...); SELECT LAST_INSERT_ID();
实际项目里最容易被忽略的是连接字符串中 Allow User Variables 和 Allow Load Local Infile 这两个开关 —— 它们默认关闭,而对应功能一旦用上又不会报配置错,只会静默失败或语法报错,排查成本很高。










