前言
日常开发中,大家是否也遇到过这样的场景:测试环境的数据太久没更新,跑出来的结果和生产环境对不上;或者客户现场部署了一个新版本,但历史数据还在老系统里,迁移起来又慢又容易出错?更头疼的是,有些客户的数据库藏在内网深处,只能通过跳板机访问,外网工具根本用不了。这时候,一个轻量、可控、能跑在特殊网络环境下的数据库同步工具,就成了刚需。
今天分享的这个工具,正是为了解决这类问题而生——它完全基于 C# 开发,开源、无依赖、功能完整,并且在最新版本中新增了增量同步能力,特别适合那些"网络受限但又必须同步数据"的真实业务场景。
为什么需要自研同步工具?
市面上当然有成熟的数据库同步方案,比如 SSIS、Redgate 或云厂商提供的 DTS 服务。但它们要么收费昂贵,要么配置复杂,更重要的是,在客户内网或通过 VPN 跳板机访问的环境下,这些外部工具往往无法运行。
而手动导出 .bak 文件或生成 INSERT 脚本,效率低、易出错,还难以自动化。
于是,我决定自己写一个:轻量、图形化、支持结构与数据同步,还能智能判断哪些数据是新增或变更的——这就是这个工具的核心出发点。
核心功能一览
智能结构同步:自动比对源表与目标表的列定义、主键、默认值约束,差异部分可选择重建或增量调整。
灵活的数据策略:支持全量覆盖、增量追加(基于主键判断)、批量分页处理,避免内存溢出。
可视化操作界面:连接配置、表选择、进度条、日志输出一目了然。
配置持久化:保存常用连接和同步选项,下次直接加载,省去重复输入。
异常容错机制:单表失败不影响整体流程,支持手动中断,日志详细可追溯。
最关键的是,新增了增量同步功能——只传输变化的数据,大幅提升效率,尤其适合每日定时同步的场景。
运行效果

配置类设计
工具首先定义了清晰的配置模型,便于序列化和用户交互:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespaceAppSqlServerSynTool
{
#region 配置和选项类
[Serializable]
publicclassSyncConfig
{
publicstring SourceConnectionString { get; set; }
publicstring TargetConnectionString { get; set; }
publicbool SyncStructure { get; set; }
publicbool SyncData { get; set; }
publicbool CreateTargetTables { get; set; }
publicbool TruncateTargetTables { get; set; }
publicbool IncrementalSync { get; set; } // 新增这个,以前版本没有
publicint BatchSize { get; set; }
public List<string> SelectedTables { get; set; }
}
publicclassSyncOptions
{
publicbool SyncStructure { get; set; }
publicbool SyncData { get; set; }
publicbool CreateTargetTables { get; set; }
publicbool TruncateTargetTables { get; set; }
publicint BatchSize { get; set; }
}
#endregion
}
通过 [Serializable] 特性,配置可直接保存为 XML 文件,方便复用。IncrementalSync 字段是本次升级的关键开关。
增量同步核心逻辑
增量同步的核心在于:如何判断一条记录是否已存在?
答案是:基于主键查询。如果目标表中不存在该主键,则插入;若存在且用户启用了"更新"选项,则执行更新。
private async Task<(int, int)> SyncWithPrimaryKeyAsync(string tableName,
DataTable sourceData, List<string> primaryKeys,
SqlConnection targetConnection, SqlTransaction transaction, int batchSize)
{
int insertCount = 0;
int updateCount = 0;
string primaryKeyWhere = string.Join(" AND ",
primaryKeys.Select(pk => $"[{pk}] = @{pk}"));
string selectExistsSql = $@"
SELECT COUNT(*) FROM [{tableName}]
WHERE {primaryKeyWhere}";
foreach (DataRow row in sourceData.Rows)
{
using (var selectCmd = new SqlCommand(selectExistsSql, targetConnection, transaction))
{
foreach (string pk in primaryKeys)
{
selectCmd.Parameters.AddWithValue($"@{pk}", row[pk] ?? DBNull.Value);
}
int existingCount = (int)await selectCmd.ExecuteScalarAsync();
if (existingCount == 0)
{
await InsertNewRecord(tableName, row, targetConnection, transaction);
insertCount++;
}
elseif (chkUpdateData.Checked)
{
await UpdateExistingRecord(tableName, row, primaryKeys,
targetConnection, transaction);
updateCount++;
}
}
}
return (insertCount, updateCount);
}
整个过程包裹在事务中,确保原子性,同时返回插入和更新的数量,便于统计。
表结构智能比对
结构同步不是简单地 DROP + CREATE,而是先做差异分析:
private async Task<bool> CompareTableStructureAsync(string tableName,
DataTable sourceColumnsSchema, List<string> sourcePrimaryKeys,
Dictionary<string, string> sourceDefaultConstraints,
SqlConnection targetConnection)
{
try
{
DataTable targetColumnsSchema = await GetTargetTableColumnsAsync(tableName, targetConnection);
List<string> targetPrimaryKeys = await GetTargetPrimaryKeysAsync(tableName, targetConnection);
if (!CompareColumns(sourceColumnsSchema, targetColumnsSchema))
{
LogMessage($"表 {tableName} 的列结构发生变化");
returntrue; // 需要重建
}
if (!ComparePrimaryKeys(sourcePrimaryKeys, targetPrimaryKeys))
{
LogMessage($"表 {tableName} 的主键结构发生变化");
returntrue; // 需要重建
}
returnfalse; // 结构无变化
}
catch (Exception ex)
{
LogMessage($"比较表 {tableName} 结构时出错: {ex.Message},将重建表");
returntrue; // 出错时选择重建表以确保安全
}
}
这种"保守但安全"的策略,避免因微小结构差异导致同步失败。
高性能批量写入
对于全量同步,使用 SqlBulkCopy 是性能最优解:
private async Task SynchronizeTableDataFullAsync(string tableName)
{
using (SqlConnection targetConnection = new SqlConnection(_targetConnectionString))
{
await targetConnection.OpenAsync();
using (SqlTransaction transaction = targetConnection.BeginTransaction())
{
try
{
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(
targetConnection, SqlBulkCopyOptions.Default, transaction))
{
bulkCopy.DestinationTableName = tableName;
bulkCopy.BatchSize = (int)numericBatchSize.Value;
bulkCopy.BulkCopyTimeout = 600;
bulkCopy.EnableStreaming = true; // 启用流式处理
foreach (DataColumn column in sourceData.Columns)
{
bulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName);
}
await bulkCopy.WriteToServerAsync(sourceData);
LogMessage($"成功向目标表 {tableName} 插入 {sourceData.Rows.Count} 行数据");
}
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
throw;
}
}
}
}
配合 EnableStreaming 和合理的 BatchSize,即使百万级数据也能平稳处理。
用户界面与体验优化
跨线程更新进度条是 WinForm 开发的经典问题,这里做了完整防护:
private void UpdateProgress(int current, int total)
{
if (this.InvokeRequired)
{
if (this.IsHandleCreated && !this.IsDisposed)
{
try
{
this.Invoke(new Action(() => UpdateProgressInternal(current, total)));
}
catch (ObjectDisposedException) { return; }
catch (InvalidOperationException) { return; }
}
}
else
{
UpdateProgressInternal(current, total);
}
}
private void UpdateProgressInternal(int current, int total)
{
if (!this.IsDisposed)
{
progressBarMain.Value = current;
labelProgress.Text = $"{current}/{total}";
toolStripProgressBar.Value = (int)((double)current / total * 100);
}
}
确保在长时间同步过程中界面不卡死、不崩溃。
实战配置示例
增量同步(日常更新)
var config = new SyncConfig
{
SourceConnectionString = "Server=prod;Database=CRM;...",
TargetConnectionString = "Server=test;Database=CRM_Test;...",
SyncStructure = true,
SyncData = true,
IncrementalSync = true, // 关键!
BatchSize = 5000
};
全量迁移(首次部署):
var migrationConfig = new SyncConfig
{
SyncStructure = true,
SyncData = true,
CreateTargetTables = true,
TruncateTargetTables = true,
IncrementalSync = false,
BatchSize = 10000
};
总结
这个数据库同步工具虽小,却凝聚了多个工程实践要点:配置管理、异常处理、事务控制、高性能 I/O、UI 线程安全等。它不是为了替代商业产品,而是为那些"工具用不了、手动太麻烦"的边缘场景提供一个可靠、透明、可调试的解决方案。
最重要的是,代码完全掌握在自己手中——你可以根据客户需求,轻松加入字段映射、数据脱敏、定时任务等扩展功能。在受限网络环境下,这种自主可控的能力,往往比功能丰富更重要。
目前该工具已在多个客户项目中稳定运行,显著减少了数据同步的人工成本和出错率。如果你也面临类似挑战,不妨试试看。
关键词
SQL Server、#数据库同步、#增量同步、C#、#SqlBulkCopy、#WinForm、#主键比对、#配置持久化、#事务处理、#开源工具