最近公司决定有一个项目从SQL Server迁移到MySQL以降低成本。面对200多张表的迁移任务,头疼了…,下次有时间改成ui的版本。
手动迁移表结构? 光是数据类型转换就能让人崩溃!
导出导入数据? 字符编码、主键冲突、数据格式不兼容…
在数据库迁移过程中都遇到过数据丢失或结构错误的问题。今天这篇文章,我将分享一套完整的C#自动化解决方案,让你轻松搞定数据库迁移!
🔍 问题分析:数据库迁移的三大痛点
😵 痛点1:数据类型映射复杂
SQL Server的nvarchar(MAX)
对应MySQL的什么类型?datetime2
又该如何转换?
😵 痛点2:表结构创建繁琐
手动写CREATE TABLE语句?一个表还好,几百个表简直是噩梦!
😵 痛点3:数据同步容易出错
字符转义、NULL值处理、大数据量传输…每一步都是坑!
🛠️ 解决方案:C#自动化迁移工具
🎯 核心设计思路
- 自动获取
- 智能映射
- 批量创建
- 安全同步
💻 代码实战:完整解决方案
🔧 项目准备
首先安装必要的NuGet包:
System.Data.SqlClient
MySql.Data

🔥 核心转换器类
using System;
using System.Data;
using System.Data.SqlClient;
using MySql.Data.MySqlClient;
using System.Text;
namespace DatabaseSync
{
publicclass DatabaseConverter
{
// 连接字符串存储
privatestring sqlServerConnStr;
privatestring mysqlConnStr;
public DatabaseConverter(string sqlServerConnStr, string mysqlConnStr)
{
this.sqlServerConnStr = sqlServerConnStr;
this.mysqlConnStr = mysqlConnStr;
}
/// <summary>
/// 一键转换主方法 - 这是整个流程的入口
/// </summary>
/// <param name="tableName">要转换的表名</param>
public void Convert(string tableName)
{
try
{
Console.WriteLine($"🚀 开始转换表:{tableName}");
// 步骤1:获取SQL Server表结构
DataTable schema = GetSqlServerTableSchema(tableName);
Console.WriteLine("✅ 表结构获取完成");
// 步骤2:创建MySQL表
CreateMySqlTable(schema, tableName);
Console.WriteLine("✅ MySQL表创建完成");
// 步骤3:同步数据
SyncData(tableName);
Console.WriteLine($"🎉 表 {tableName} 转换完成!");
}
catch (Exception ex)
{
Console.WriteLine($"❌ 转换过程出错:{ex.Message}");
throw;
}
}
}
}
📊 表结构获取方法
/// <summary>
/// 从SQL Server获取完整表结构信息
/// 包括:列名、数据类型、长度、是否允许NULL、是否自增等
/// </summary>
private DataTable GetSqlServerTableSchema(string tableName)
{
using (SqlConnection conn = new SqlConnection(sqlServerConnStr))
{
conn.Open();
// 🔍 关键SQL:获取表的完整结构信息
string query = @"
SELECT
COLUMN_NAME, -- 列名
DATA_TYPE, -- 数据类型
CHARACTER_MAXIMUM_LENGTH, -- 最大长度
IS_NULLABLE, -- 是否允许NULL
COLUMN_DEFAULT, -- 默认值
CASE WHEN COLUMNPROPERTY(OBJECT_ID(@TableName), COLUMN_NAME, 'IsIdentity') = 1
THEN 'YES'
ELSE 'NO'
END AS IS_IDENTITY -- 是否自增
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
ORDER BY ORDINAL_POSITION";
using (SqlCommand cmd = new SqlCommand(query, conn))
{
// 🛡️ 使用参数化查询防止SQL注入
cmd.Parameters.AddWithValue("@TableName", tableName);
DataTable schema = new DataTable();
schema.Load(cmd.ExecuteReader());
return schema;
}
}
}
🏗️ MySQL表创建方法
/// <summary>
/// 根据SQL Server表结构创建对应的MySQL表
/// 自动处理数据类型映射和约束转换
/// </summary>
private void CreateMySqlTable(DataTable schema, string tableName)
{
using (MySqlConnection conn = new MySqlConnection(mysqlConnStr))
{
conn.Open();
StringBuilder createTableSql = new StringBuilder();
createTableSql.AppendLine($"CREATE TABLE IF NOT EXISTS `{tableName}` (");
// 🔄 遍历所有列,构建CREATE TABLE语句
for (int i = 0; i < schema.Rows.Count; i++)
{
DataRow row = schema.Rows[i];
string columnName = row["COLUMN_NAME"].ToString();
string dataType = row["DATA_TYPE"].ToString();
string maxLength = row["CHARACTER_MAXIMUM_LENGTH"].ToString();
string isNullable = row["IS_NULLABLE"].ToString();
string isIdentity = row["IS_IDENTITY"].ToString();
// 构建列定义
createTableSql.Append($"`{columnName}` {ConvertDataType(dataType, maxLength)}");
// 🚀 处理自增属性
if (isIdentity == "YES")
{
createTableSql.Append(" AUTO_INCREMENT");
}
// 🔒 处理NULL约束
if (isNullable == "NO")
{
createTableSql.Append(" NOT NULL");
}
// 添加逗号分隔符(最后一列除外)
if (i < schema.Rows.Count - 1)
{
createTableSql.AppendLine(",");
}
}
// 🔑 添加主键(假设第一列为主键)
createTableSql.AppendLine($",PRIMARY KEY (`{schema.Rows[0]["COLUMN_NAME"]}`)");
createTableSql.AppendLine(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
using (MySqlCommand cmd = new MySqlCommand(createTableSql.ToString(), conn))
{
cmd.ExecuteNonQuery();
}
}
}
🔄 数据类型智能映射
/// <summary>
/// SQL Server数据类型到MySQL数据类型的智能映射
/// 这是迁移成功的关键!
/// </summary>
private string ConvertDataType(string sqlServerType, string maxLength, bool isPrimaryKey = false)
{
switch (sqlServerType.ToLower())
{
// 🔢 整数类型映射
case"int":
return"int";
case"bigint":
return"bigint";
case"smallint":
return"smallint";
case"tinyint":
return"tinyint";
case"bit":
return"bit";
// 💰 数值类型映射
case"decimal":
case"numeric":
return"decimal(18,2)";
case"float":
return"float";
// 📅 日期时间类型映射
case"datetime":
case"datetime2":
return"datetime";
case"date":
return"date";
case"time":
return"time";
// 📝 字符串类型映射
case"char":
case"nchar":
return $"char({maxLength})";
case"varchar":
case"nvarchar":
if (maxLength == "-1") // MAX类型
{
// 如果是主键,限制长度
return isPrimaryKey ? "varchar(255)" : "text";
}
else
{
int length = int.Parse(maxLength);
// 如果是主键且长度过大,限制为255
if (isPrimaryKey && length > 255)
{
return"varchar(255)";
}
return $"varchar({maxLength})";
}
case"text":
case"ntext":
// 如果是主键,使用varchar(255)
return isPrimaryKey ? "varchar(255)" : "text";
case"uniqueidentifier":
return"varchar(36)";
// 🔧 默认处理
default:
return isPrimaryKey ? "varchar(255)" : "text";
}
}
📦 数据同步核心方法
/// <summary>
/// 核心数据同步方法 - 处理大数据量迁移
/// 包含字符转义、NULL值处理等关键逻辑
/// </summary>
private void SyncData(string tableName)
{
using (SqlConnection sqlConn = new SqlConnection(sqlServerConnStr))
using (MySqlConnection mysqlConn = new MySqlConnection(mysqlConnStr))
{
sqlConn.Open();
mysqlConn.Open();
// 📖 读取源数据
using (SqlCommand sqlCmd = new SqlCommand($"SELECT * FROM {tableName}", sqlConn))
using (SqlDataReader reader = sqlCmd.ExecuteReader())
{
DataTable schemaTable = reader.GetSchemaTable();
if (reader.HasRows)
{
MySqlCommand mysqlCmd = new MySqlCommand();
mysqlCmd.Connection = mysqlConn;
int recordCount = 0;
// 🔄 逐行处理数据
while (reader.Read())
{
StringBuilder insertSql = new StringBuilder();
insertSql.Append($"INSERT INTO `{tableName}` (");
// 🏷️ 构建列名部分
for (int i = 0; i < schemaTable.Rows.Count; i++)
{
string columnName = schemaTable.Rows[i]["ColumnName"].ToString();
insertSql.Append($"`{columnName}`");
if (i < schemaTable.Rows.Count - 1) insertSql.Append(",");
}
insertSql.Append(") VALUES (");
// 💾 构建值部分
for (int i = 0; i < reader.FieldCount; i++)
{
if (reader.IsDBNull(i))
{
insertSql.Append("NULL");
}
else
{
// 🔍 获取列的数据类型
string dataTypeName = schemaTable.Rows[i]["DataTypeName"].ToString();
object value = reader.GetValue(i);
// 📅 特殊处理日期时间类型
if (IsDateTimeType(dataTypeName))
{
DateTime dateTime = System.Convert.ToDateTime(value);
// 转换为MySQL标准格式:YYYY-MM-DD HH:mm:ss
string formattedDate = dateTime.ToString("yyyy-MM-dd HH:mm:ss");
insertSql.Append($"'{formattedDate}'");
}
else
{
if(dataTypeName == "bit")
{
var bitValue = value.ToString()=="true" ? 1 : 0;
insertSql.Append($"{bitValue}");
}
else
{
string stringValue = value.ToString();
// 🛡️ SQL注入防护 - 转义单引号
insertSql.Append($"'{stringValue.Replace("'", "''")}'");
}
}
}
if (i < reader.FieldCount - 1) insertSql.Append(",");
}
insertSql.Append(")");
mysqlCmd.CommandText = insertSql.ToString();
mysqlCmd.ExecuteNonQuery();
recordCount++;
// 📊 进度提示
if (recordCount % 1000 == 0)
{
Console.WriteLine($"已处理 {recordCount} 条记录...");
}
}
Console.WriteLine($"✅ 数据同步完成,共处理 {recordCount} 条记录");
}
}
}
}
/// <summary>
/// 判断是否为日期时间类型
/// </summary>
private bool IsDateTimeType(string dataTypeName)
{
string[] dateTimeTypes = {
"DateTime", "DateTime2", "Date", "Time",
"SmallDateTime", "DateTimeOffset"
};
return dateTimeTypes.Contains(dataTypeName, StringComparer.OrdinalIgnoreCase);
}
🎯 使用示例
using System.Text;
namespace AppSqlserver2Mysql
{
internal class Program
{
static void Main(string[] args)
{
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
try
{
// 🔗 配置连接字符串
string sqlServerConnStr = "Server=.;Database=**;Trusted_Connection=True;";
string mysqlConnStr = "Server=***;Database=test;Uid=root;Pwd=Iseeyou123;";
// 🏭 创建转换器实例
DatabaseConverter converter = new DatabaseConverter(sqlServerConnStr, mysqlConnStr);
// 🚀 开始转换(可以批量处理多个表)
string[] tablesToConvert = { "wms_user", "wms_basic_material", "wms_basic_location" };
foreach (string tableName in tablesToConvert)
{
converter.Convert(tableName);
Console.WriteLine($"✅ {tableName} 迁移完成!");
}
Console.WriteLine("🎉 所有表迁移完成!");
}
catch (Exception ex)
{
Console.WriteLine($"❌ 程序执行失败:{ex.Message}");
}
}
}
}

⚠️ 重要提醒:生产环境注意事项
🔒 1. 安全性考虑
🚀 2. 性能优化
// 💡 性能优化示例:批量插入
private void SyncDataInBatches(string tableName, int batchSize = 1000)
{
// 使用MySQL的批量插入语法提升性能
// INSERT INTO table VALUES (1,'a'),(2,'b'),(3,'c')...
}
📊 3. 监控与日志
✨ 总结:三个关键收获
🎯 1. 自动化是王道
手动迁移100张表?不如花2小时写个工具,让程序跑一夜!
🎯 2. 数据类型映射是核心
掌握SQL Server到MySQL的类型转换规则,这是迁移成功的关键。
🎯 3. 安全性不能忽视
字符转义、参数化查询、权限控制…每一个细节都关乎数据安全。上面例子其实最好参数化会好不少。
💬 互动时间:
- 你们团队在数据库迁移时遇到过哪些坑?
- 除了SQL Server到MySQL,还需要其他数据库的迁移方案吗?
觉得这篇文章对你有帮助的话,记得转发给更多需要的同行!让我们一起告别手动迁移的痛苦时代!🚀
阅读原文:https://mp.weixin.qq.com/s/ITJZXvzX3rwdn6t1Q6iyLQ
该文章在 2025/7/10 15:31:26 编辑过