目錄
1.前言
2.BULK INSERT
3.簡單示例
前言
由於昨天接到一個客戶反饋導出數據卡死的問題,於是決定今天模擬一下千萬級的數據,然后傻傻的等待插入數據了半天......
對於海量數據,上百萬上千萬的數據插入,我們用ADO.NET提供的普通一條一條數據插入非常非常慢,好在Sql Server為我們提供了批量插入方法。
BULK INSERT
語法
主要參數說明
database_name
指定的表或視圖所在的數據庫的名稱,如果未指定,則默認為當前數據庫。
schema_name
表或視圖架構的名稱。
table_name
要將數據大容量導入其中的表或視圖的名稱。
‘data_file’
數據文件的完整路徑,該數據文件包含到導入到指定表或視圖中的數據。使用BULK INSERT可以從磁盤導入數據。
BATCHSIZE=batch_size
指定批量處理中的行數。每個批處理作為一個事物復制到服務器。
CHECK_CONSTRAINTS
指定在大容量導入操作期間,必須檢查所有對目標表或視圖的約束。
FIELDTERMINATOR ='field_terminator'
指定要用於 char 和 widechar 數據文件的字段終止符,即字段的分隔符。 默認的字段終止符是 \t(制表符)。
ROWTERMINATOR ='row_terminator'
指定要用於 char 和 widechar 數據文件的行終止符,即行的分隔符。
更多參數說明,請參考: https://msdn.microsoft.com/zh-cn/library/ms188365.aspx
簡單示例
為了對比BULK INSERT和普通逐條插入的差異,我們通過一個簡單的示例,通過實際運行來查看效果。
第一步:在數據庫新建兩張一樣的表,分表為Student和Student1,表結構完全相同,只有ID,NAME,AGE三個簡單的字段。
第二步:新建一個控制台程序,通過一個簡單的循環,生成500000條數據寫入到txt文件中,關鍵代碼如下:

/// <summary> /// 生成測試數據 /// </summary> private static void GenerateTestData() { string fileName = "sql"; int i = 1; while (i <= 500000) { string strInsert = string.Format("{0},'test{0}',{0}|", i); File.AppendText(strInsert, fileName); i++; } }
第三步:封裝出兩個方法,分別用來執行批量插入和普通插入,具體代碼如下:

/// <summary> /// 批量插入測試 /// </summary> private static void BulkInsertTest() { string strFilePath = @"D:\學習\ASP.NET\QYH.BlukInsertTest\sql.txt"; string strTableName = "Student"; /* 每一個字段的信息以“,”分割 *每一條數據以“|”符號分隔 * 每10萬條數據一個事務*/ string sql = string.Format("BULK INSERT {0} FROM '{1}' WITH (FIELDTERMINATOR = ',',ROWTERMINATOR ='|',BATCHSIZE = 50000)", strTableName, strFilePath); DBHelper dbHelper = new DBHelper(); dbHelper.Excute(sql); } /// <summary> /// 普通插入測試 /// </summary> private static void CommonInsertTest() { int i = 1; while (i <= 500000) { string sqlInsert = string.Format("insert into Student1(id,Name,Age) values({0},'test{0}',{0})", i); new DBHelper().Excute(sqlInsert); i++; } }
第四步:Main主函數中調用批量插入和普通插入方法,並通過Stopwatch計算出執行時間,Pragram完整代碼如下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using QYH.BlukInsertTest.FileMange; using QYH.BlukInsertTest.DataBase; using System.Diagnostics; namespace QYH.BlukInsertTest { class Program { static void Main(string[] args) { //用於生成海量數據 //GenerateTestData(); Stopwatch stopwatch = Stopwatch.StartNew(); try { BulkInsertTest(); } catch (Exception) { //throw; } stopwatch.Stop(); string strResult = "批量插入耗時:" + stopwatch.ElapsedMilliseconds.ToString(); Stopwatch stopwatch1 = Stopwatch.StartNew(); CommonInsertTest(); stopwatch1.Stop(); string str1Result = "普通插入耗時:" + stopwatch1.ElapsedMilliseconds.ToString(); string strTestResult = "result"; File.WriteTextAsync(strResult + "\r\n" + str1Result, strTestResult); //Console.Read(); } /// <summary> /// 批量插入測試 /// </summary> private static void BulkInsertTest() { string strFilePath = @"D:\學習\ASP.NET\QYH.BlukInsertTest\sql.txt"; string strTableName = "Student"; /* 每一個字段的信息以“,”分割 *每一條數據以“|”符號分隔 * 每10萬條數據一個事務*/ string sql = string.Format("BULK INSERT {0} FROM '{1}' WITH (FIELDTERMINATOR = ',',ROWTERMINATOR ='|',BATCHSIZE = 50000)", strTableName, strFilePath); DBHelper dbHelper = new DBHelper(); dbHelper.Excute(sql); } /// <summary> /// 普通插入測試 /// </summary> private static void CommonInsertTest() { int i = 1; while (i <= 500000) { string sqlInsert = string.Format("insert into Student1(id,Name,Age) values({0},'test{0}',{0})", i); new DBHelper().Excute(sqlInsert); i++; } } /// <summary> /// 生成測試數據 /// </summary> private static void GenerateTestData() { string fileName = "sql"; int i = 1; while (i <= 500000) { string strInsert = string.Format("{0},'test{0}',{0}|", i); File.AppendText(strInsert, fileName); i++; } } } }
示例中還用到兩個輔助類,DBHelper.cs和File.cs,由於僅用於演示,所以寫的非常簡單,其中文件路徑是寫死的,可以替換成實際路徑。
DBHelper.cs

using System; using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; using System.Text; using System.Threading.Tasks; namespace QYH.BlukInsertTest.DataBase { public class DBHelper { public string connectionString = "Server=.;Database=QYHDB;User ID=sa;Password=123456;Trusted_Connection=False;"; public void Excute(string sql) { SqlConnection conn = new SqlConnection(connectionString); SqlCommand command = new SqlCommand(); command.CommandTimeout = 0; command.Connection = conn; command.CommandText = sql; conn.Open(); command.ExecuteNonQuery(); conn.Close(); } } }
File.cs

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace QYH.BlukInsertTest.FileMange { public class File { public static string strFilePath = @"D:\學習\ASP.NET\QYH.BlukInsertTest"; public static async void WriteTextAsync(string text, string fileName) { using (StreamWriter outputFile = new StreamWriter(strFilePath + @"\" + fileName + ".txt")) { await outputFile.WriteAsync(text); } } public static void AppendText(string text, string fileName) { // Append text to an existing file named "WriteLines.txt". using (StreamWriter outputFile = new StreamWriter(strFilePath + @"\" + fileName + ".txt",true)) { outputFile.WriteLine(text); } } } }
一切准備就緒,開始運行,結果如下:
其中單位為毫秒,從結果我們可以看出BULK INSER插入500000條數據還不需要3秒,而普通逐條插入卻需要20多分鍾。