Operasi massal dengan go-mssqldb

Driver go-mssqldb mendukung operasi penyisipan massal berkinerja tinggi dengan menggunakan fungsi mssql.CopyIn. Penyisipan massal melewati alur normal INSERT baris demi baris dan mengirimkan data secara langsung ke server menggunakan protokol penyalinan massal TDS.

Pilih salinan massal, TVP, atau JSON

Gunakan panduan berikut saat Anda perlu mengirim beberapa baris atau muatan kompleks ke SQL Server:

Pilih... Kapan paling cocok Tradeoff
Salin massal dengan mssql.CopyIn Anda memerlukan cara tercepat untuk memuat banyak baris ke dalam satu tabel target. Throughput paling tinggi, tetapi ditujukan untuk pemuatan tabel, bukan kontrak stored procedure atau payload dengan struktur campuran.
Parameter bernilai tabel Anda perlu meneruskan kumpulan baris yang diketik dengan kuat ke dalam prosedur tersimpan atau perintah berparameter. Mempertahankan batas skema dan prosedur, tetapi memerlukan jenis tabel yang ditentukan pengguna dan urutan bidang yang cocok.
JSON dengan OPENJSON atau FOR JSON Aplikasi Anda sudah bertukar JSON, atau bentuk payload bersarang atau fleksibel. Lebih portabel untuk kode aplikasi, tetapi biasanya lebih lambat dan kurang aman terhadap tipe data dibandingkan TVP atau penyalinan massal untuk operasi insert terstruktur.

Jika Anda memuat data dalam jumlah besar ke tabel staging atau tabel tujuan, mulailah dengan penyalinan massal. Jika Anda memanggil prosedur tersimpan dengan sekumpulan baris data terstruktur, mulailah dengan TVP. Jika Anda memerlukan dokumen berlapis atau skema longgar, mulailah dengan JSON.

Contoh dalam artikel ini menggunakan basis data sampel AdventureWorks2025. Contoh salinan massal target HumanResources.Department dan Production.ProductCategory.

Sisipan massal dasar

Gunakan mssql.CopyIn untuk membuat pernyataan salinan massal, lalu gunakan Exec untuk mengirim baris:

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()
}

Pemanggilan terakhir stmt.Exec() tanpa argumen mengosongkan baris yang masih tersisa dan menyelesaikan operasi penyalinan massal.

Opsi Massal

mssql.BulkOptions Struktur ini mengonfigurasi perilaku salin massal:

Ladang Type Description
CheckConstraints bool Periksa batasan selama penyisipan massal.
FireTriggers bool Picu trigger INSERT pada tabel target.
KeepNulls bool Pertahankan nilai null alih-alih menyisipkan nilai default.
KilobytesPerBatch int Kilobyte per batch. 0 menggunakan default bawaan server.
RowsPerBatch int Baris per kelompok. 0 menggunakan setelan bawaan server.
Order []string ORDER petunjuk untuk indeks kluster target (misalnya, []string{"Id ASC"}).
Tablock bool Gunakan kunci pada tingkat tabel selama proses penyalinan massal.

Contoh dengan opsi

Gunakan BulkOptions untuk mengendalikan pemeriksaan batasan, trigger, dan penguncian:

stmt, err := txn.Prepare(mssql.CopyIn("HumanResources.Department",
    mssql.BulkOptions{
        CheckConstraints: true,
        FireTriggers:     true,
        Tablock:          true,
        RowsPerBatch:     1000,
    },
    "Name", "GroupName"))

Penanganan kesalahan

Jika ada baris yang gagal, seluruh operasi penyalinan massal gagal. Periksa kesalahan baik dari panggilan Exec di setiap baris maupun dari flush terakhir 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
}

Mendeteksi dan mencatat baris yang gagal

Ketika operasi penyalinan massal gagal, pesan kesalahan dari SQL Server menunjukkan batasan atau masalah data tetapi tidak mengidentifikasi baris tertentu. Untuk mengidentifikasi baris yang gagal, gunakan pendekatan batching:

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

Jika Anda perlu melewati baris yang bermasalah dan melanjutkan, gunakan pernyataan INSERT individual atau pola tabel penahapan: lakukan salin massal ke tabel penahapan tanpa batasan, lalu gunakan MERGE atau INSERT...SELECT dengan penanganan kesalahan untuk memindahkan data ke tabel target.

Streaming dari file CSV

Untuk file CSV besar, streaming baris langsung dari file ke salinan massal tanpa memuat seluruh file ke dalam memori:

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()
}

Perbandingan performa

Salinan massal secara signifikan lebih cepat daripada sisipan individual untuk beban data besar. Tabel berikut menunjukkan perkiraan karakteristik performa untuk menyisipkan 100.000 baris:

Metode Kecepatan relatif Bolak-balik jaringan Penguncian
Individu INSERT Paling lambat (1x) 100,000 Tingkat baris per sisipan.
Diproses secara batch INSERT (1.000 baris per pernyataan) Sedang (5-10x) 100 Tingkat baris per batch.
Salin massal tanpa TABLOCK Cepat (20–50×) Tergantung pada ukuran batch Massal pada tingkat baris.
Salin massal dengan TABLOCK Tercepat (50-100x) Tergantung pada ukuran batch Penguncian tingkat tabel, pencatatan minimal.

Note

Performa aktual bervariasi berdasarkan latensi jaringan, konfigurasi server, indeks tabel, dan apakah pengelogan minimal tersedia. Lakukan benchmark dengan beban kerja spesifik Anda menggunakan testing.B. Lihat Penyetelan performa.

Urutan kolom dan pemetaan jenis

Kolom di mssql.CopyIn harus cocok dengan urutan dan jenis yang diharapkan oleh tabel target. Driver tidak melakukan pencocokan nama kolom; Ini menggunakan pemetaan posisi.

Masalah jenis umum

Pergi ketik Kolom SQL Server Issue Solusi
string varchar Konversi implisit nvarchar. Gunakan mssql.VarChar pembungkus.
float64 decimal(18,4) Kehilangan presisi. Lanjutkan sebagai string.
time.Time datetime2 Konversi zona waktu. Gunakan waktu UTC.
nil Setiap kolom yang dapat bernilai NULL Membutuhkan KeepNulls: true. Atur KeepNulls dalam BulkOptions.

Contoh dengan jenis eksplisit

Tentukan jenis kolom secara eksplisit saat pemetaan jenis default tidak cocok dengan skema Anda:

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
    }
}

Tips kinerja

  • Penggunaan Tablock untuk sisipan besar ke dalam tabel kosong. Opsi ini mengurangi pertentangan kunci dan memungkinkan pencatatan minimal.
  • Mengatur RowsPerBatch untuk mengontrol seberapa sering driver mengirim data. Batch yang lebih besar mengurangi perjalanan pulang pergi tetapi menggunakan lebih banyak memori.
  • Meningkatkan packet size di string koneksi (hingga 32767) untuk mengurangi overhead jaringan.
  • Urutkan data agar sesuai dengan indeks berkluster tabel target dan atur opsi.Order Pendekatan ini menghindari pengurutan di sisi server.
  • Hapus indeks nonclustered sebelum pemuatan data massal dalam jumlah besar, lalu bangun ulang setelahnya. Pemeliharaan indeks selama penyisipan massal menambah overhead.
  • Penggunaan CheckConstraints: false (default) untuk data tepercaya untuk melewati pemeriksaan batasan selama salinan massal.

Keterbatasan

  • Salinan massal tidak mendukung kolom yang dilindungi oleh Always Encrypted. Untuk informasi selengkapnya, lihat Batasan.
  • Salinan massal tingkat TDS tidak didukung di Azure SQL Database. Untuk Azure SQL Database, gunakan pernyataan batch INSERT atau pola tabel penahapan sebagai gantinya.