Opérations en bloc avec go-mssqldb

Le pilote go-mssqldb prend en charge les opérations d’insertion en vrac hautes performances à l’aide de la fonction mssql.CopyIn. L’insertion en bloc contourne le chemin normal ligne par ligne INSERT et transmet les données directement au serveur en utilisant le protocole TDS de copie en masse.

Choisissez la copie en masse, TVP ou JSON

Utilisez le guide suivant lorsque vous devez envoyer plusieurs lignes ou des charges utiles complexes vers SQL Server :

Choisir... Quand cela vous convient le mieux Compromis
Copie en masse avec mssql.CopyIn Il faut le moyen le plus rapide de charger plusieurs lignes dans une seule table cible. Meilleur débit, mais il vise les charges de table plutôt que les contrats de procédure stockée ou les charges utiles de formes mixtes.
Un paramètre à valeurs de table Vous devez passer un ensemble fortement typé de lignes dans une procédure stockée ou une commande paramétrée. Préserve les limites du schéma et des procédures, mais nécessite un type de table défini par l’utilisateur et un ordre de champs correspondant.
JSON avec OPENJSON ou FOR JSON Votre application échange déjà du JSON, ou la forme de la charge utile est imbriquée ou flexible. Plus portable pour le code applicatif, mais généralement plus lent et moins sûr du point de vue des types que les TVP ou la copie en bloc pour les insertions structurées.

Si vous chargez de gros lots dans une table de staging ou de destination, commencez par une copie en masse. Si vous appelez des procédures stockées avec des ensembles de lignes structurées, commencez par les TVP. Si vous avez besoin de documents imbriqués ou de schémas lâches, commencez par le JSON.

Les exemples de cet article s’exécutent sur la base de données d’exemple AdventureWorks2025. Les exemples de copie en bloc ciblent HumanResources.Department et Production.ProductCategory.

Insertion en masse de base

Utilisez mssql.CopyIn pour créer une instruction de copie en masse, puis utilisez Exec pour envoyer des lignes :

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

L’appel final stmt.Exec() sans arguments élimine les lignes restantes et complète l’opération de copie en masse.

Options par lot

La mssql.BulkOptions structure configure le comportement de la copie en bloc :

Champ Catégorie Description
CheckConstraints bool Vérifiez les contraintes lors de l’insertion en masse.
FireTriggers bool Déclenche INSERT les déclencheurs sur la table cible.
KeepNulls bool Préserver les valeurs nulles au lieu d’insérer des valeurs par défaut.
KilobytesPerBatch int Kilo-octets par lot. 0 utilise le serveur par défaut.
RowsPerBatch int Lignes par lot. 0 utilise le serveur par défaut.
Order []string ORDER indication pour l’index clusterisé cible (par exemple, []string{"Id ASC"}).
Tablock bool Obtenez un verrou au niveau de la table pendant la durée de la copie en masse.

Exemple avec des options

Passez BulkOptions pour contrôler les vérifications des contraintes, les déclencheurs et le verrouillage :

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

Gestion des erreurs

Si une ligne échoue, toute l’opération de copie en bloc échoue. Vérifiez les erreurs à la fois dans l’appel Exec de chaque ligne ainsi que dans le vidage final 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
}

Détecter et enregistrer les lignes échouées

Lorsqu'une opération de copie en masse échoue, le message d'erreur de SQL Server indique la contrainte ou le problème de données mais n'identifie pas la ligne spécifique. Pour identifier les lignes défaillantes, utilisez une approche par lots :

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

Si vous devez sauter les mauvaises lignes et continuer, utilisez des instructions individuelles INSERT ou un schéma de table de staging : copiez en masse dans une table de staging sans contraintes, puis utilisez un MERGE ou INSERT...SELECT avec gestion d’erreurs pour déplacer les données vers la table cible.

Diffuser à partir de fichiers CSV

Pour les gros fichiers CSV, diffusez les lignes directement du fichier vers une copie en bloc sans charger l’intégralité du fichier en mémoire :

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

Comparaison entre les performances

La copie en vrac est nettement plus rapide que les insertions individuelles pour de grandes charges de données. Le tableau suivant présente les caractéristiques de performance approximatives pour insérer 100 000 lignes :

Méthode Vitesse relative Allers-retours du réseau Verrouillage
Particulier INSERT Le plus lent (1x) 100 000 Au niveau de la ligne pour chaque insertion.
Par lots INSERT (1 000 lignes par instruction) Moyen (5 à 10 fois) 100 Au niveau de la ligne, par lot.
Copie groupée sans TABLOCK Rapide (20-50x) Cela dépend de la taille du lot Volume au niveau des rangées.
Copie en masse avec TABLOCK Le plus rapide (50-100x) Cela dépend de la taille du lot Verrouillage au niveau de la table, journalisation minimale.

Note

Les performances réelles varient en fonction de la latence du réseau, de la configuration du serveur, des index de table et de la disponibilité de la journalisation minimale. Faites un benchmark avec votre charge de travail spécifique en utilisant testing.B. Consultez Réglage des performances.

Ordre des colonnes et mappage de types

Les colonnes dans mssql.CopyIn doivent correspondre à l’ordre et aux types attendus par la table cible. Le pilote n’effectue pas de correspondance des noms de colonnes ; il utilise un mappage positionnel.

Problèmes de type courants

type Go Colonne SQL Server Problème Solution
string varchar Conversion implicite nvarchar. Utilisez le wrapper mssql.VarChar.
float64 decimal(18,4) Perte de précision. Faites passer pour string.
time.Time datetime2 Conversion de fuseau horaire. Utilisez les heures UTC.
nil Toute colonne nullable Exige KeepNulls: true. Défini KeepNulls dans BulkOptions.

Exemple avec des types explicites

Spécifiez explicitement les types de colonnes lorsque la correspondance par défaut ne correspond pas à votre schéma :

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

Astuces pour les performances

  • Utilisez Tablock pour les insertions volumineuses dans des tables vides. Cette option réduit la contention sur les verrous et permet une journalisation minimale.
  • Décor RowsPerBatch pour contrôler la fréquence à laquelle le conducteur envoie les données. Les lots plus volumineux réduisent les allers-retours mais consomment plus de mémoire.
  • Augmentez packet size dans la chaîne de connexion (jusqu’à 32767) pour réduire la surcharge du réseau.
  • Commander les données pour qu’elles correspondent à l’index regroupé de la table cible et définir l’option Order . Cette approche évite un tri côté serveur.
  • Supprimez les index non regroupés avant les charges en masse importantes, puis reconstruisez-les après. La maintenance de l’index lors d’une insertion massive entraîne un surcoût.
  • Utilisez CheckConstraints: false (par défaut) pour ignorer la vérification des contraintes lors de la copie en bloc pour des données fiables.

Limitations

  • Bulk copy ne prend pas en charge les colonnes protégées par Always Encrypted. Pour plus d’informations, consultez Limitations.
  • La copie en masse au niveau TDS n'est pas prise en charge sur Azure SQL Database. Pour Azure SQL Database, utilisez plutôt des instructions en lots INSERT ou un motif de table de staging.