简介:本资源是一份面向VB.NET初学者与数据迁移开发者的实战型代码示例包,聚焦解决Windows平台下连接MySQL数据库并批量导入CSV文件的核心需求,适用于教学演示、小型数据处理系统开发及数据库对接实训场景。压缩包共29个文件,含6个核心VB源码(.vb)、1个Visual Studio解决方案(.sln)、1个项目配置文件(.vbproj)、3个可执行程序(.exe)及配套资源文件(.resx/.xml/.dll等),完整呈现从连接建立、CSV解析、SQL插入到异常捕获与事务控制的全流程实现。资源体积仅75KB,轻量易部署,结构清晰,便于逐模块理解ADO.NET与MySql.Data驱动协同机制。目前已有193人学习下载,读者可直接复用项目框架,快速掌握VB.NET操作MySQL的关键API调用、TextFieldParser文本解析技巧、批处理导入优化策略及典型错误处理范式。
1. VB.NET 连接 MySQL 并导入 CSV:为什么“双击运行就报错”是绝大多数新手的第一道墙?
你手头有个.zip文件,解压后是MYSQL_connect_csv_h.vbproj或类似命名的 VB.NET 工程,目标很明确:用 Windows 桌面程序读取本地 CSV 文件(比如data.csv),把每一行数据塞进远程或本机的 MySQL 表里。但双击exe就弹窗:“未找到提供程序”“连接字符串无效”“Could not load file or assembly 'MySql.Data'”,甚至 CSV 读到一半就卡死在带中文、逗号、换行符的字段上——这不是你代码写错了,而是 VB.NET + MySQL + CSV 这个三角组合,从环境链路、字符编码、驱动版本到 CSV 解析逻辑,存在四层隐性断点。它不考验算法深度,但极度考验工程落地的完整链路意识。本文面向两类人:一是刚从 C# 转 VB.NET、对 ADO.NET 基础尚可但没碰过 MySQL 驱动的开发者;二是被业务催着“今天必须把这 5000 行销售数据导进系统”的一线实施工程师。我们不讲抽象的 ConnectionString 构造原理,只聚焦:怎么让 ZIP 包里的代码,在你 Win10/Win11 机器上,不改一行源码,就能跑通第一笔数据。核心就三件事:装对驱动、写对连接串、绕开 CSV 的“玄学字段分隔”。
2. 环境筑基:VB.NET 项目必须装的不是“MySQL”,而是 MySql.Data.dll 的精确版本
VB.NET 本身不认 MySQL。它只认 ADO.NET 接口。真正干活的是第三方实现——MySql.Data这个 .NET 数据提供程序(DataProvider)。它的版本、部署方式、GAC 注册状态,直接决定你的Imports MySql.Data.MySqlClient是绿灯还是红叉。
2.1 为什么 NuGet 安装常翻车?——目标框架与驱动版本的硬约束
VB.NET 项目有TargetFramework(如net472,net6.0-windows,net8.0-windows)。而MySql.Data的支持矩阵极不友好:
MySql.Data 8.0.x:仅支持 .NET Core 3.1+ / .NET 5+,对传统net472项目会静默失败(编译过,运行时报System.IO.FileNotFoundException);MySql.Data 6.10.x:最后支持 .NET Framework 4.x 的稳定版,但默认不兼容 TLS 1.2(现代 MySQL 8.0 默认强制 TLS 1.2);MySqlConnector(开源替代):完全跨平台,支持net472到net8.0,且 TLS 1.2 开箱即用,但需手动替换命名空间。
提示:打开你的
.vbproj文件,找<TargetFramework>标签。这是你选驱动的唯一依据,不是看 MySQL 服务端版本。
2.2 实操:三步锁定并安装正确驱动(以 net472 项目为例)
假设你的项目是传统的 Windows Forms 应用,.vbproj中写着<TargetFramework>net472</TargetFramework>,请严格按此流程操作:
# 步骤1:卸载所有可能冲突的旧包(在 Visual Studio "Package Manager Console" 中执行) Uninstall-Package MySql.Data -Force Uninstall-Package MySqlConnector -Force # 步骤2:安装经验证的 net472 兼容版(注意:不是最新版!) Install-Package MySql.Data -Version 6.10.9 # 步骤3:检查 bin\Debug 下是否生成了 MySql.Data.dll(关键!) # 如果没有,右键项目 → "属性" → "引用" → 手动添加 bin\packages\MySql.Data.6.10.9\lib\net452\MySql.Data.dll参数说明:
-Version 6.10.9是net452编译的二进制,向下兼容net472,且已内置 TLS 1.2 支持补丁(官方 6.10.9 发布说明明确提及);- 不要用
8.0.33,它生成的 DLL 依赖System.Runtime.CompilerServices.Unsafe,而net472项目若未显式安装该包,运行时必崩; MySqlConnector虽好,但需将代码中所有MySql.Data.MySqlClient替换为MySqlConnector,对 ZIP 包原始代码改动大,首通不推荐。
2.3 连接字符串不是“填空题”,而是“协议协商书”
很多教程让你抄server=localhost;user=root;pwd=123;database=test,但这在真实环境中大概率失效。原因在于 MySQL 8.0+ 默认认证插件从mysql_native_password改为caching_sha2_password,而MySql.Data 6.10.9不原生支持后者。
必须显式降级用户认证方式(在 MySQL 命令行中执行):
-- 登录 MySQL(用 root 或高权限账号) ALTER USER 'your_user'@'%' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;然后你的连接字符串才真正有效:
' VB.NET 中的连接字符串(务必用双引号包裹,且不能换行) Dim connectionString As String = "server=192.168.1.100;port=3306;user=your_user;password=your_password;database=test_db;charset=utf8mb4;SslMode=none;"关键参数说明:
charset=utf8mb4:强制客户端使用 UTF-8 四字节编码,避免中文存成??;SslMode=none:关闭 SSL(开发环境可接受;生产环境应配证书并设SslMode=Required);port=3306:显式写出,避免某些内网 MySQL 实例监听非标端口;- 不要加
Allow User Variables=True或UseCompression=True,这些在 6.10.9 中有已知内存泄漏。
3. CSV 解析:别信 TextFieldParser,用 StreamReader + Split 的血泪经验
VB.NET 自带Microsoft.VisualBasic.FileIO.TextFieldParser,文档写得天花乱坠,号称自动处理引号、换行、逗号。但实战中,它在以下场景必然翻车:
- CSV 第 123 行有个字段含
"abc,def"(带逗号的字符串),TextFieldParser会错误切分成两列; - CSV 含 BOM 头(
EF BB BF),TextFieldParser读出第一列是id(带不可见字符),导致INSERT INTO报 “Unknown column 'id'”; - 某行末尾多了一个换行符
\r\n\r\n,TextFieldParser直接抛MalformedLineException,中断整个导入。
我一般会弃用TextFieldParser,改用StreamReader手动逐行读取 +Split,虽土但稳。核心逻辑:用"作为字段边界标记,而非依赖Split(",")。
3.1 可复用的 CSV 行解析函数(VB.NET)
''' <summary> ''' 安全解析 CSV 行,正确处理含逗号、引号、换行的字段 ''' </summary> ''' <param name="line">原始 CSV 行(已 TrimEnd)</param> ''' <returns>字符串数组,每个元素为一个字段值(已去引号、去空白)</returns> Public Function ParseCsvLine(line As String) As String() Dim fields As New List(Of String) Dim currentField As New System.Text.StringBuilder Dim inQuotes As Boolean = False Dim i As Integer = 0 While i < line.Length Dim c As Char = line(i) If c = """"c Then inQuotes = Not inQuotes ElseIf c = ","c AndAlso Not inQuotes Then ' 字段分隔符,且不在引号内 fields.Add(currentField.ToString().Trim("""c")) currentField.Clear() Else currentField.Append(c) End If i += 1 End While ' 添加最后一个字段 fields.Add(currentField.ToString().Trim("""c")) Return fields.ToArray() End Function逻辑说明:
- 它不依赖正则或复杂状态机,用纯布尔标志
inQuotes控制逗号是否生效; Trim("""c")移除字段两端的英文双引号("),这是 CSV 标准要求;- 对
""(两个连续引号)这种转义写法暂不支持(真实业务 CSV 极少用,若需支持,可在inQuotes=True且c=""""c时检查下一个字符是否也是""",然后跳过)。
3.2 完整导入流程:从文件到数据库的最小闭环
Private Sub ImportCsvToMysql(filePath As String, tableName As String) Dim connectionString As String = "server=192.168.1.100;port=3306;user=importer;password=pass123;database=prod_db;charset=utf8mb4;SslMode=none;" Using conn As New MySqlConnection(connectionString) Try conn.Open() ' 1. 读取 CSV 文件(必须指定 UTF-8 with BOM 编码) Using reader As New StreamReader(filePath, Encoding.UTF8) Dim headerLine As String = reader.ReadLine() If headerLine Is Nothing Then Throw New Exception("CSV 文件为空") ' 2. 解析表头,生成 INSERT SQL 模板 Dim headers() As String = ParseCsvLine(headerLine) Dim placeholders As String = String.Join(",", headers.Select(Function(h) $"@{h}")) Dim sql As String = $"INSERT INTO {tableName} ({String.Join(",", headers)}) VALUES ({placeholders})" ' 3. 预编译命令,防 SQL 注入 Using cmd As New MySqlCommand(sql, conn) ' 为每个字段添加参数(避免类型推断错误) For Each h In headers cmd.Parameters.AddWithValue($"@{h}", "") Next ' 4. 逐行读取并执行 Dim lineNumber As Integer = 2 ' 从第2行开始(header占第1行) While Not reader.EndOfStream Dim line As String = reader.ReadLine() If String.IsNullOrWhiteSpace(line) Then Continue While Dim values() As String = ParseCsvLine(line) If values.Length <> headers.Length Then MessageBox.Show($"第 {lineNumber} 行字段数不匹配(期望{headers.Length},实际{values.Length})") Continue While End If ' 绑定参数值(关键:显式转换为 DB 类型) For i As Integer = 0 To headers.Length - 1 Dim param As MySqlParameter = cmd.Parameters($"@{headers(i)}") ' 数字字段转 Decimal,日期转 DateTime,其余转 String If IsNumeric(values(i)) AndAlso Not String.IsNullOrEmpty(values(i)) Then param.Value = Convert.ToDecimal(values(i)) ElseIf IsDate(values(i)) AndAlso Not String.IsNullOrEmpty(values(i)) Then param.Value = Convert.ToDateTime(values(i)) Else param.Value = values(i) ' 字符串直接赋值 End If Next cmd.ExecuteNonQuery() lineNumber += 1 End While End Using End Using MessageBox.Show($"成功导入 {lineNumber - 2} 行数据") Catch ex As MySqlException MessageBox.Show($"MySQL 错误: {ex.Message} (SQL State: {ex.SqlState})") Catch ex As IOException MessageBox.Show($"文件读取错误: {ex.Message}") End Try End Using End Sub参数与行为说明:
Encoding.UTF8:强制用 UTF-8 读取,兼容带 BOM 的 Excel 导出 CSV;cmd.Parameters.AddWithValue初始化时传空字符串,避免DBNull问题;IsNumeric/IsDate类型判断放在循环内,确保每行独立校验;lineNumber从 2 开始计,方便定位错误行;MessageBox.Show用于桌面程序调试,生产环境应改用日志记录。
4. 避坑:VB.NET 连 MySQL 导 CSV 的 5 个高频翻车现场
这些不是理论问题,是我在某高校实验室部署 12 套同类系统时,被反复踩过的坑。每一条都附带现象、根因和一招解决。
4.1 现象:程序能连 MySQL,但INSERT时提示 “Data too long for column 'name' at row 1”
原因:CSV 中某字段含不可见字符(如U+200B零宽空格),Len()返回长度正常,但 MySQLVARCHAR(50)实际存储字节数超限(UTF8MB4 下一个中文占 4 字节)。
解决:在ParseCsvLine函数末尾,对每个字段值加清洗:
value = Regex.Replace(value, "[\u200B-\u200D\uFEFF]", "") ' 清除零宽字符 value = value.Trim() ' 再 Trim 空格、制表符4.2 现象:导入后中文全变成???,但用 Navicat 手动插入中文却正常
原因:MySQL 服务端character_set_server是latin1,而连接字符串写了charset=utf8mb4,但驱动未生效。
解决:在连接字符串末尾追加;Connection Timeout=30;,并确认 MySQL 配置文件my.cnf中有:
[mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci重启 MySQL 服务。
4.3 现象:CSV 第 1 行是表头,但程序把表头当数据插进去了
原因:reader.ReadLine()被调用两次——一次在headerLine = reader.ReadLine(),另一次在While Not reader.EndOfStream循环开头又读了一次。
解决:删除循环内的reader.ReadLine(),改为:
Dim line As String = reader.ReadLine() ' 在 while 内第一行就读 If line Is Nothing Then Exit While4.4 现象:导入大数据量(>10 万行)时内存暴涨,程序假死
原因:MySqlCommand默认开启AutoPrepare,每行都重新编译 SQL,且MySql.Data 6.10.9有参数缓存泄漏。
解决:在Using cmd块内,显式关闭自动准备:
cmd.Prepare() ' 手动预编译一次 ' 然后在循环内只调用 cmd.ExecuteNonQuery(),不再 Prepare4.5 现象:CSV 含日期字段如2023/05/20,导入后 MySQL 中变成0000-00-00
原因:MySQL 的DATE类型严格要求YYYY-MM-DD格式,2023/05/20被视为非法。
解决:在IsDate(values(i))分支中,不直接Convert.ToDateTime,而是标准化:
Dim dt As DateTime = Convert.ToDateTime(values(i)) param.Value = dt.ToString("yyyy-MM-dd") ' 强制输出标准格式5. 性能与健壮性进阶:批量插入、事务控制与断点续传
单行INSERT在导入万级数据时,速度感人(实测 5000 行约 42 秒)。真正的生产级方案必须上批量(Bulk)和事务。但MySql.Data不支持SqlBulkCopy(那是 SQL Server 的),得用LOAD DATA INFILE或拼接多值INSERT。前者需要 MySQL 服务端文件权限,后者更可控。
5.1 用INSERT ... VALUES (),(),()实现百倍提速
核心思路:攒够 1000 行,拼成一条 SQL 执行,而非 1000 条单独 SQL。
' 修改 ImportCsvToMysql 函数中的循环部分: Dim batchValues As New List(Of String) Dim batchCount As Integer = 0 Const BATCH_SIZE As Integer = 1000 While Not reader.EndOfStream Dim line As String = reader.ReadLine() If String.IsNullOrWhiteSpace(line) Then Continue While Dim values() As String = ParseCsvLine(line) If values.Length <> headers.Length Then Continue While ' 构建单行 VALUES 字符串:('val1','val2',123) Dim valueParts As New List(Of String) For i As Integer = 0 To headers.Length - 1 Dim v As String = If(String.IsNullOrEmpty(values(i)), "NULL", $"'{values(i).Replace("'", "''")}'") valueParts.Add(v) Next batchValues.Add($"({String.Join(",", valueParts)})") batchCount += 1 ' 达到批次,执行批量插入 If batchCount >= BATCH_SIZE Then Dim batchSql As String = $"INSERT INTO {tableName} ({String.Join(",", headers)}) VALUES {String.Join(",", batchValues)}" Using cmd As New MySqlCommand(batchSql, conn) cmd.ExecuteNonQuery() End Using batchValues.Clear() batchCount = 0 End If End While ' 插入剩余不足 BATCH_SIZE 的行 If batchValues.Count > 0 Then Dim batchSql As String = $"INSERT INTO {tableName} ({String.Join(",", headers)}) VALUES {String.Join(",", batchValues)}" Using cmd As New MySqlCommand(batchSql, conn) cmd.ExecuteNonQuery() End Using End If关键细节:
values(i).Replace("'", "''"):简单防 SQL 注入(生产环境建议用参数化,但批量拼接下参数化不适用,此为折中);BATCH_SIZE = 1000:MySQL 默认max_allowed_packet=4M,1000 行 × 平均 200 字节 ≈ 200KB,安全;- 不用
UNION ALL,因INSERT ... VALUES语法更简洁、性能更好。
5.2 加事务:要么全成功,要么全回滚
在conn.Open()后、循环前,显式开启事务:
Using transaction As MySqlTransaction = conn.BeginTransaction() Try ' ... 所有 INSERT 语句都绑定到 transaction ... cmd.Transaction = transaction ' 批量插入后 transaction.Commit() Catch ex As Exception transaction.Rollback() Throw ' 重新抛出,由外层捕获 End Try End Using5.3 断点续传:记录已处理行号,崩溃后从断点继续
在导入前,先查表当前最大id(假设主键自增),再读 CSV 时跳过前 N 行:
' 查询当前最大 ID Dim maxIdSql As String = $"SELECT COALESCE(MAX(id), 0) FROM {tableName}" Using cmd As New MySqlCommand(maxIdSql, conn) Dim maxId As Integer = CInt(cmd.ExecuteScalar()) ' 跳过 CSV 前 maxId 行(需 CSV 有序且 id 字段对应) For i As Integer = 1 To maxId reader.ReadLine() ' 丢弃 Next End Using我的习惯是:首次导入用小批量(100 行)+ 详细日志;确认逻辑无误后,再切到 1000 行批量 + 事务;上线前必加断点续传。因为客户不会等你重跑 2 小时——他们只关心“现在卡在哪一行”。希望帮到你。
本文还有配套的精品资源,点击获取