前言 最近公司在使用 ABP 重構之前的老項目,資料庫也由 SQL SERVER 切換到了 MySql。吐槽一下,之前的產品使用的是 Windows Server 2008 , SqlServer 2008R2, .Net Framework 4.5,現在開始擁抱 .net core。回到正題。目前 ...
前言
最近公司在使用 ABP
重構之前的老項目,資料庫也由 SQL SERVER
切換到了 MySql
。吐槽一下,之前的產品使用的是 Windows Server 2008
, SqlServer 2008R2
, .Net Framework 4.5
,現在開始擁抱 .net core
。回到正題。目前單表有 10w+
,100w+
數據不等,等會都測試一下。資料庫切換,以及資料庫表結構變化,不可以避免的需要進行數據遷移。而遷移方案也並不是很多,下麵是我嘗試使用的兩種方案進行測試。
多線程批量寫入
private static async Task BatchInsertTestUsers(List<TestUser> testUsers)
{
var prefix =
"INSERT INTO users (Id,Name,Age) VALUES";
using (IDbConnection conn = new MySqlConnection(DataMigrationConfig.MySqlConstr))
{
var sqlText = new StringBuilder();
sqlText.Append(prefix);
foreach (var testUser in testUsers)
{
sqlText.AppendFormat(
$"({testUser.Id},'{testUser.Name}', {testUser.Age}),");
}
var insertSql = sqlText.ToString().Substring(0, sqlText.ToString().LastIndexOf(','));
await conn.ExecuteAsync(insertSql);
}
}
BatchInsertTestUsers
將傳入的集合,拼接成SQL
並執行。
public static Task RunMultiTasks(List<TestUser> users)
{
var tasks = new List<Task>();
var pageSize = 10000;
var writeCount = (users.Count() / pageSize) + 2;
for (var i = 1; i < writeCount; i++)
{
var skipCount = (i - 1) * pageSize;
var batchInsertList = users.Skip(skipCount).Take(pageSize).ToList();
var task = Task.Run(() => { BatchInsertTestUsers(batchInsertList); });
tasks.Add(task);
}
var sw = new Stopwatch();
sw.Start();
Task.WaitAll(tasks.ToArray());
sw.Stop();
Console.WriteLine($"多線程批量插入用時:{sw.ElapsedMilliseconds} ms");
return Task.FromResult(0);
}
RunMultiTasks
將數據分批,一次性插入1w
條。
MySqlBulkLoader 方案
瞭解到 MySqlBulkLoader
是因為 SqlServer
的 Sqlbulkcopy
。MySqlBulkLoader
並不支持集合的導入,需要先將數據導出為 .csv
格式,然後讀取 .csv
數據導入。
public static async Task Export(string filePath, List<TestUser> items)
{
IExporter exporter = new CsvExporter();
await exporter.Export(filePath, items);
}
- 這裡數據導出使用國人開源的 dotnetcore/Magicodes.IE 我這個導出代碼,應該就懂了吧!操作簡潔!!!
public static void Load(string filePath, string tableName)
{
using MySqlConnection conn = new MySqlConnection(DataMigrationConfig.MySqlConstr);
var bulk = new MySqlBulkLoader(conn)
{
FieldTerminator = ",",
FieldQuotationCharacter = '"',
EscapeCharacter = '"',
LineTerminator = "\r\n",
FileName = filePath,
Local = true,
NumberOfLinesToSkip = 1,
TableName = tableName,
CharacterSet = "utf8mb4",
};
bulk.Load();
}
- 這裡因為資料庫並不在自己本機上,所以設置了
Local = true
讀取本地文件,進行導入。
測試說明
- 這個測試是在我本地測試的,資料庫是跑在內網部署的一臺機器上的
Docker
容器內,用的是機械硬碟。如果您的使用的是SSD
硬碟,效果會更佳。 - 這裡測試主要是插入簡單的用戶數據,定義如下:
public class TestUser
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
- 分別測試
1w
,10w
,100w
條數據插入的性能,以及開啟索引以及關閉索引的影響 - 測試執行代碼如下:
class Program
{
static async Task Main(string[] args)
{
var testData = DataGen.Run(100 * 10000);
await RunMultiTasks(testData);
await RunMySqlLoaderTask(testData);
}
public static async Task RunMultiTasks(List<TestUser> users)
{
await DataMigrateTask.RunMultiTasks(users);
}
public static async Task RunMySqlLoaderTask(List<TestUser> users)
{
var fileName = "users";
var filePath = Directory.GetCurrentDirectory() + "\\" + fileName + ".csv";
await DataMigrateTask.Export(filePath, users);
var sw = new Stopwatch();
sw.Start();
DataMigrateTask.Load(filePath, "users");
sw.Stop();
Console.WriteLine($"MySqlBulkLoader 用時:{sw.ElapsedMilliseconds} ms");
}
}
測試結果
說了那麼多,這裡才是最重點。
方案 | 1w | 10w | 100w |
---|---|---|---|
RunMultiTasks | 367ms | 3548ms | 91263ms |
RunMySqlLoaderTask | 2031ms | 1597ms | 13105ms |
RunMultiTasks(關閉索引) | 233ms | 3230ms | 67040ms |
RunMySqlLoaderTask (關閉索引) | 1785ms | 1367ms | 12456ms |
最後
以上的測試僅供參考,上面的簡單測試一下,數據量大的時候 MySqlLoaderTask
優勢是明顯的,對於小於 1w
數據量的可以採用多線程批量插入效果更好。有興趣的小伙伴的可以自己下載代碼玩玩。如有更好的
方案,不吝賜教。
- 代碼地址:DataMigrationTest
坑
MySqlLoader
導入null
數據使用NULL
,而不是mysql
文檔上說的\N