go-mssqldb驅動程式透過使用該mssql.CopyIn函式支援高效能的大量插入操作。 批量插入繞過了通常的逐列 INSERT 路徑,直接透過 TDS 批量複製協定將資料串流到伺服器。
選擇批量複製、TVP 或 JSON
當你需要將多列或複雜負載傳送到 SQL Server 時,請參考以下指南:
| 選擇...... | 當它最適合時 | 權衡取捨 |
|---|---|---|
批量複製 mssql.CopyIn |
你需要最快的方式將多列載入一個目標表。 | 吞吐量最佳,但它適用於資料表載入,而非預存程序的呼叫介面或混合結構的酬載。 |
| 一個 表值參數 | 你需要將一組強型別資料列傳遞至預存程序或參數化命令。 | 保留結構與程序邊界,但需要使用者自訂的資料表類型及匹配的欄位順序。 |
搭配 OPENJSON 或 FOR JSON 的 JSON |
你的應用程式已經交換 JSON,或者 payload 形狀是巢狀或彈性的。 | 對於應用程式程式碼來說,它更具可攜性,但通常比 TVP 或結構化插入的批量複製更慢且型別安全度較低。 |
如果您要將大量資料載入暫存資料表或目標資料表,請先使用大量複製。 如果你呼叫的是結構化列集的儲存程序,建議先從 TVP 開始。 如果你需要巢狀文件或鬆散的結構,建議先從 JSON 開始。
本文範例與 AdventureWorks2025 範例資料庫對比。 批次複製範例的目標為 HumanResources.Department 和 Production.ProductCategory。
基本的散裝內襯
使用 mssql.CopyIn 來建立大量複製陳述式,然後使用 Exec 來傳送資料列:
import (
"database/sql"
"log"
"github.com/microsoft/go-mssqldb"
)
func bulkInsert(db *sql.DB) error {
txn, err := db.Begin()
if err != nil {
return err
}
defer txn.Rollback()
stmt, err := txn.Prepare(mssql.CopyIn("HumanResources.Department", mssql.BulkOptions{},
"Name", "GroupName"))
if err != nil {
return err
}
// Add rows
_, err = stmt.Exec("Data Science", "Research and Development")
if err != nil {
return err
}
_, err = stmt.Exec("Cloud Ops", "Information Technology")
if err != nil {
return err
}
_, err = stmt.Exec("Developer Relations", "Sales and Marketing")
if err != nil {
return err
}
// Flush and finalize the bulk copy
result, err := stmt.Exec()
if err != nil {
return err
}
if err = stmt.Close(); err != nil {
return err
}
rowsAffected, _ := result.RowsAffected()
log.Printf("Bulk inserted %d rows\n", rowsAffected)
return txn.Commit()
}
最後 stmt.Exec() 一次呼叫且無參數時會清除剩餘列,並完成批量複製操作。
批量選擇權
結構體 mssql.BulkOptions 配置批量複製行為:
| Field | 類型 | Description |
|---|---|---|
CheckConstraints |
bool |
在批量插入時檢查限制。 |
FireTriggers |
bool |
在目標資料表上觸發 INSERT 觸發程序。 |
KeepNulls |
bool |
保留空值,而非插入預設值。 |
KilobytesPerBatch |
int |
每批次的 KB 數。
0 使用伺服器預設值。 |
RowsPerBatch |
int |
每批次的列數。
0 使用伺服器預設值。 |
Order |
[]string |
ORDER 針對目標叢集索引的提示(例如, []string{"Id ASC"})。 |
Tablock |
bool |
在大量複製期間取得資料表層級鎖定。 |
選項範例
傳遞 BulkOptions 以控制約束檢查、觸發程序和鎖定:
stmt, err := txn.Prepare(mssql.CopyIn("HumanResources.Department",
mssql.BulkOptions{
CheckConstraints: true,
FireTriggers: true,
Tablock: true,
RowsPerBatch: 1000,
},
"Name", "GroupName"))
錯誤處理
若有任何一列失敗,整個批量複製操作即告失敗。 檢查來自每一列的 Exec 呼叫及最終清空 Exec 的錯誤:
for _, emp := range employees {
_, err = stmt.Exec(emp.Name, emp.GroupName)
if err != nil {
txn.Rollback()
return err
}
}
// Final flush
_, err = stmt.Exec()
if err != nil {
txn.Rollback()
return err
}
偵測並記錄失敗的列
當批次複製操作失敗時,SQL Server 會顯示限制或資料問題,但不會標示具體哪一列。 要識別失敗的列,請使用批次處理方法:
func bulkInsertWithRowTracking(db *sql.DB, departments []Department) error {
txn, err := db.Begin()
if err != nil {
return err
}
stmt, err := txn.Prepare(mssql.CopyIn("HumanResources.Department",
mssql.BulkOptions{RowsPerBatch: 500}, "Name", "GroupName"))
if err != nil {
txn.Rollback()
return err
}
for i, dept := range departments {
_, err = stmt.Exec(dept.Name, dept.GroupName)
if err != nil {
txn.Rollback()
log.Printf("Bulk copy failed at row %d (Name=%q): %v", i, dept.Name, err)
return fmt.Errorf("bulk copy failed at row %d: %w", i, err)
}
}
_, err = stmt.Exec()
if err != nil {
txn.Rollback()
return fmt.Errorf("bulk copy flush failed: %w", err)
}
if err = stmt.Close(); err != nil {
txn.Rollback()
return err
}
return txn.Commit()
}
Tip
如果你需要略過錯誤資料列並繼續處理,可以使用個別的 INSERT 陳述式或暫存資料表模式:先大量複製到沒有條件約束的暫存資料表,然後使用含錯誤處理的 MERGE 或 INSERT...SELECT,將資料移轉到目標資料表。
從 CSV 檔案串流
對於大型 CSV 檔案,直接將資料列串流到批量複製,而不必將整個檔案載入記憶體:
import (
"encoding/csv"
"io"
"os"
)
func bulkInsertFromCSV(db *sql.DB, filePath string) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
reader := csv.NewReader(f)
// Skip the header row.
_, err = reader.Read()
if err != nil {
return err
}
txn, err := db.Begin()
if err != nil {
return err
}
stmt, err := txn.Prepare(mssql.CopyIn("HumanResources.Department",
mssql.BulkOptions{Tablock: true, RowsPerBatch: 5000},
"Name", "GroupName"))
if err != nil {
txn.Rollback()
return err
}
var rowCount int
for {
record, err := reader.Read()
if err == io.EOF {
break
}
if err != nil {
txn.Rollback()
return fmt.Errorf("CSV read error at row %d: %w", rowCount+1, err)
}
_, err = stmt.Exec(record[0], record[1])
if err != nil {
txn.Rollback()
return fmt.Errorf("row %d: %w", rowCount+1, err)
}
rowCount++
}
// Flush remaining rows.
result, err := stmt.Exec()
if err != nil {
txn.Rollback()
return err
}
if err = stmt.Close(); err != nil {
txn.Rollback()
return err
}
affected, _ := result.RowsAffected()
log.Printf("Bulk inserted %d rows from CSV", affected)
return txn.Commit()
}
表現比較
批量複製在大量資料負載下,比單一插入快得多。 下表顯示插入 100,000 列時的大致效能特性:
| 方法 | 相對速度 | 網絡往返列車 | 鎖定 |
|---|---|---|---|
個人 INSERT |
最慢 (1倍) | 100,000 | 每個插入點按行級計算。 |
批次處理 INSERT (每個語句 1,000 列) |
中等(5-10倍) | 100 | 每批次的行級。 |
不含 TABLOCK 的批量複製 |
快速(20-50倍) | 這要看批次大小 | 排級體積。 |
使用 TABLOCK 進行大量複製 |
最快(50-100x) | 這要看批次大小 | 桌上鎖,記錄最少。 |
Note
實際效能會根據網路延遲、伺服器設定、資料表索引,以及是否有最小的日誌功能而有所不同。 使用 testing.B 針對您的特定工作負載進行基準測試。 請參閱 效能調整。
欄位排序與型態映射
欄位 mssql.CopyIn 必須符合目標資料表預期的順序與類型。 驅動程式不會執行欄位名稱匹配;它使用位置映射。
常見類型問題
| Go 型別 | SQL Server 欄位 | Issue | 解決方法 |
|---|---|---|---|
string |
varchar |
隱含 nvarchar 轉換。 |
使用 mssql.VarChar 包裝器。 |
float64 |
decimal(18,4) |
精度損失。 | 以string身分通過。 |
time.Time |
datetime2 |
時區轉換。 | 使用UTC時間。 |
nil |
任何可為 Null 的欄位 | 需要 KeepNulls: true。 |
在 KeepNulls 中設定 BulkOptions。 |
明確型態的範例
當預設型別映射與你的架構不符時,明確指定欄位類型:
stmt, err := txn.Prepare(mssql.CopyIn("Production.ProductCategory",
mssql.BulkOptions{KeepNulls: true},
"Name"))
if err != nil {
return err
}
for _, p := range categories {
_, err = stmt.Exec(p.Name)
if err != nil {
return err
}
}
效能祕訣
- 使用
Tablock對空白資料表進行大量插入。 此選項減少鎖爭用並減少日誌記錄。 -
設定
RowsPerBatch,以控制驅動程式傳送資料的頻率。 較大批次減少往返次數,但使用更多記憶體。 - 將連線字串中的 Increase
packet size提高至最高 32767,以減少網路額外負荷。 -
將資料排序 為與目標資料表的叢集索引相符,並設定選項
Order。 這種方法避免了伺服器端排序。 - 在大量載入前丟棄非叢集索引,然後再重新建立。 批量插入時的索引維護會增加額外負擔。
-
使用
CheckConstraints: false(預設值)來對受信任的資料在大量複製期間略過約束檢查。
Limitations
- 批量複製不支援受 Always Encrypted 保護的欄位。 如需詳細資訊,請參閱限制。
- Azure SQL Database 不支援 TDS 層級的批量複製。 對於 Azure SQL Database,請改用批次
INSERT陳述式或暫存資料表模式來代替。