Operações em massa com go-mssqldb

O controlador go-mssqldb suporta operações de inserção em massa de elevado desempenho através da utilização da função mssql.CopyIn. A inserção em massa contorna o caminho normal linha a INSERT linha e transmite dados diretamente para o servidor usando o protocolo de cópia em massa TDS.

Escolha cópia em massa, TVP ou JSON

Use o seguinte guia quando precisar de enviar várias linhas ou cargas complexas para o SQL Server:

Escolher... Quando for mais adequado Compromisso
Cópia em lote com mssql.CopyIn Precisa da maneira mais rápida para carregar muitas linhas numa única tabela de destino. Melhor rendimento, mas visa cargas de tabela em vez de contratos de procedimentos armazenados ou cargas úteis de formas mistas.
Um parâmetro com valores de tabela Precisa de passar um conjunto fortemente tipado de linhas para um procedimento armazenado ou comando parametrizado. Preserva os limites do esquema e do procedimento, mas requer um tipo de tabela definido pelo utilizador e uma ordem de campo correspondente.
JSON com OPENJSON ou FOR JSON A tua aplicação já troca dados em JSON, ou a estrutura da carga útil é aninhada ou flexível. Mais portátil para código de app, mas geralmente mais lento e menos seguro para escrever do que TVPs ou cópias em massa para inserções estruturadas.

Se estiveres a carregar grandes lotes numa tabela de preparação ou de destino, começa com cópia em massa. Se está a chamar procedimentos armazenados com conjuntos de linhas estruturados, comece pelos TVPs. Se precisares de documentos aninhados ou esquemas soltos, começa pelo JSON.

Os exemplos deste artigo são executados na base de dados de exemplo AdventureWorks2025. Exemplos de cópia em massa têm como objetivo HumanResources.Department e Production.ProductCategory.

Inserção básica em massa

Use mssql.CopyIn para criar uma instrução de cópia em massa e depois use Exec para enviar linhas:

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

A chamada final stmt.Exec() , sem argumentos, limpa as linhas restantes e completa a operação de cópia em massa.

BulkOptions

A mssql.BulkOptions estrutura configura o comportamento de cópia em massa:

Campo Tipo Description
CheckConstraints bool Verifique as restrições durante a inserção em massa.
FireTriggers bool Disparar INSERT gatilhos na mesa de alvo.
KeepNulls bool Preservar valores nulos em vez de inserir valores por defeito.
KilobytesPerBatch int Kilobytes por lote. 0 Usa o padrão do servidor.
RowsPerBatch int Linhas por lote. 0 Usa o padrão do servidor.
Order []string ORDER sugestão para o índice agrupado de destino (por exemplo, []string{"Id ASC"}).
Tablock bool Adquira um bloqueio ao nível da tabela durante a duração da cópia em massa.

Exemplo com opções

Passe BulkOptions para controlar verificações de restrições, gatilhos e bloqueios:

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

Tratamento de erros

Se alguma linha falhar, toda a operação de cópia em massa falha. Verifique os erros tanto da chamada de cada linha Exec como do descarregamento 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
}

Detetar e registar linhas falhadas

Quando uma operação de cópia em massa falha, a mensagem de erro do SQL Server indica a restrição ou problema de dados, mas não identifica a linha específica. Para identificar linhas falhadas, use uma abordagem de agrupamento:

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

Se precisares de ignorar linhas inválidas e continuar, usa instruções individuais INSERT ou um padrão de tabela de preparação: faz uma cópia em massa para uma tabela de preparação sem restrições e, em seguida, usa um MERGE ou INSERT...SELECT com tratamento de erros para mover os dados para a tabela de destino.

Transmitir a partir de ficheiros CSV

Para ficheiros CSV grandes, transmita as linhas diretamente do ficheiro para cópias em massa sem carregar o ficheiro inteiro na memória:

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

Comparação de desempenho

A cópia em massa é significativamente mais rápida do que inserções individuais para cargas de dados grandes. A tabela seguinte mostra características aproximadas de desempenho para inserir 100.000 linhas:

Método Velocidade relativa Viagens de ida e volta da rede Bloqueio
Individual INSERT Mais lento (1x) 100,000 Ao nível da linha, por inserção.
Em lote INSERT (1.000 linhas por instrução) Médio (5-10x) 100 Nível de linha por lote.
Cópia em massa sem TABLOCK Rápido (20-50x) Depende do tamanho do lote Volume ao nível das filas.
Cópia em lote com TABLOCK Mais rápido (50-100x) Depende do tamanho do lote Bloqueio ao nível da mesa, registo mínimo.

Note

O desempenho real varia consoante a latência da rede, configuração do servidor, índices de tabelas e se há registos mínimos disponíveis. Faz benchmarks com a tua carga de trabalho específica usando testing.B. Consulte Ajuste de desempenho.

Ordenação das colunas e mapeamento de tipos

As colunas em mssql.CopyIn devem corresponder à ordem e aos tipos esperados pela tabela alvo. O controlador não efetua a correspondência entre nomes de colunas; utiliza mapeamento posicional.

Questões de tipos comuns

Tipo Go Coluna do SQL Server Issue Solução
string varchar Conversão implícita nvarchar. Usa mssql.VarChar invólucro.
float64 decimal(18,4) Perda de precisão. Passar por string.
time.Time datetime2 Conversão de fuso horário. Usa os horários UTC.
nil Qualquer coluna que aceita valores nulos Requer KeepNulls: true. Definir KeepNulls em BulkOptions.

Exemplo com tipos explícitos

Especifique os tipos de colunas explicitamente quando o mapeamento de tipos padrão não corresponder ao seu esquema:

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

Sugestões de desempenho

  • Utilização Tablock para inserções grandes em tabelas vazias. Esta opção reduz a contenção de bloqueios e permite um registo mínimo.
  • Conjunto RowsPerBatch para controlar com que frequência o driver envia dados. Lotes maiores reduzem viagens de ida e volta, mas consomem mais memória.
  • Aumente packet size na cadeia de ligação (até 32767) para reduzir a sobrecarga de rede.
  • Ordene os dados para corresponderem ao índice agrupado da tabela alvo e defina a Order opção. Esta abordagem evita uma ordenação no lado do servidor.
  • Elimina índices não agrupados antes de grandes cargas em massa e depois reconstrói-os. A manutenção do índice durante a inserção em massa implica uma sobrecarga adicional.
  • Utilize CheckConstraints: false (a predefinição) para dados fidedignos, de modo a ignorar a verificação de restrições durante a operação de cópia em massa.

Limitations

  • O Bulk Copy não suporta colunas protegidas pelo Always Encrypted. Para obter mais informações, consulte Limitações.
  • Cópias em massa ao nível TDS não são suportadas no Base de Dados SQL do Azure. Para o Base de Dados SQL do Azure, utilize instruções agrupadas INSERT ou, em alternativa, um padrão de tabela de preparação.