千萬級甚至億級數據處理,單表數十 GB 是常態。核心鏈路:SELECT → 處理 → INSERT。小數據量小時級完成,大數據量可能耗費數天。本文從實戰角度拆解極致優化路徑。
| 層級 | 代表 | 特點 | 海量場景 |
|---|---|---|---|
| 重型 ORM | EF, NHibernate | 功能全、開發效率高 | ❌ 反射開銷大、SQL 不可控 |
| 輕量 ORM | Dapper, PetaPoco | 單文件、性能尚可 | ❌ IL 緩存仍有反射開銷 |
| 原生 ADO.NET | MySqlConnection/DataReader | 零封裝、完全可控 | ✅ 唯一可行方案 |
using (var conn = new MySqlConnection(connStr))
{
conn.Open();
// 關鍵:解決海量讀取超時
using (var cfgCmd = new MySqlCommand(
"SET net_write_timeout=9999999; SET net_read_timeout=9999999", conn))
{
cfgCmd.ExecuteNonQuery();
}
using (var cmd = new MySqlCommand())
{
cmd.Connection = conn;
cmd.CommandText = "SELECT `f1`, `f2` FROM `table1`";
cmd.CommandTimeout = 99999999; // 命令級超時
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
// 關鍵:使用序數索引而非列名,避免字典查找
var f1 = reader.GetInt32(0);
var f2 = reader.GetString(1);
// 業務處理邏輯...
}
}
}
}
GetInt32(0) 比 GetInt32("f1") 快 30%+(省去名稱解析)net_read_timeout + 命令級 CommandTimeoutSELECT f1,f2 而非 SELECT *,減少網絡傳輸與解析開銷StringBuilder/Span<T> 避免頻繁分配RegexOptions.Compiled,複雜邏輯考慮手寫解析器Parallel.ForEach 或 TPL Dataflow,注意數據庫連接不共享| 方式 | 原理 | 性能 | 適用性 |
|---|---|---|---|
| 單條 INSERT | 每條語句一次網絡往返 | 最差 | 極少量數據 |
| 事務批量 | BeginTransaction 提交一次 | 中等 | 萬級以內 |
| 多值合併 INSERT | 單條 SQL 包含多行 VALUES | 最優 | 百萬級以上首選 |
| Load Data Infile | 本地文件直接導入 | 極致 | 離線導入、ETL 場景 |
const int MaxPacketSize = 1024 * 1024; // 1MB 安全閾值,實際受 max_allowed_packet 限制
var sqlBuilder = new StringBuilder();
const string SqlHeader = "INSERT INTO `table1` (`f1`, `f2`) VALUES ";
using (var conn = new MySqlConnection(connStr))
{
conn.Open();
// 同樣設置超時...
using (var readCmd = new MySqlCommand("SELECT `f1`, `f2` FROM `source_table`", conn))
using (var reader = readCmd.ExecuteReader())
using (var writeCmd = conn.CreateCommand())
{
writeCmd.CommandTimeout = 99999999;
sqlBuilder.Append(SqlHeader);
bool firstValue = true;
while (reader.Read())
{
var f1 = reader.GetInt32(0);
var f2 = reader.GetString(1);
// 業務處理...
var processedF2 = ProcessData(f2);
if (!firstValue) sqlBuilder.Append(',');
firstValue = false;
// 參數化防注入,此處為演示用字符串拼接,生產請用參數化
sqlBuilder.AppendFormat("({0}, '{1}')", f1, EscapeSingleQuote(processedF2));
// 達到閾值批量提交
if (sqlBuilder.Length >= MaxPacketSize)
{
writeCmd.CommandText = sqlBuilder.ToString();
writeCmd.ExecuteNonQuery();
sqlBuilder.Clear();
sqlBuilder.Append(SqlHeader);
firstValue = true;
}
}
// 尾批提交
if (!firstValue)
{
writeCmd.CommandText = sqlBuilder.ToString();
writeCmd.ExecuteNonQuery();
}
}
}
// 輔助方法
string EscapeSingleQuote(string input) => input.Replace("'", "''");
string ProcessData(string raw) { /* 業務邏輯 */ return raw; }
max_allowed_packet:啟動時 SHOW VARIABLES LIKE 'max_allowed_packet' 獲取實際限制,預留 20% 安全邊界MySqlParameter,拼接 SQL 僅作演示ExecuteNonQueryAsync 配合生產者-消費者模式,讀取與寫入解耦並行當前方案雖高效但代碼耦合度高,下一步重構重點:
BulkInserter<T> 泛型組件,內建批次控制、參數化、異步寫入IDataReader 裝飾器模式,統一序數映射與類型轉換System.Threading.Channels 實現零拷貝管道流海量數據 MySQL 交互的核心鐵律:
這些「古老」技術在極限場景下依然是性能王者,關鍵在於工程化落地的細節把控。