Operações em massa com go-mssqldb

O driver go-mssqldb oferece suporte a operações de inserção em massa com alto desempenho usando a função mssql.CopyIn. O bulk insert contorna o caminho normal linha por linha INSERT 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 guia a seguir quando precisar enviar várias linhas ou cargas complexas para o SQL Server:

Escolher... Quando melhor se encaixa Vantagens e desvantagens
Cópia em lote com mssql.CopyIn Você precisa da forma mais rápida de carregar muitas linhas para uma única tabela de destino. Melhor taxa de transferência, mas é voltado para carregamentos de tabelas em vez de contratos de procedimentos armazenados ou payloads de formato misto.
Um parâmetro com valores de tabela Você precisa passar um conjunto de linhas fortemente tipado para um procedimento armazenado ou comando parametrizado. Preserva os limites do esquema e do procedimento, mas requer um tipo de tabela definido pelo usuário e uma ordem de campo correspondente.
JSON com OPENJSON ou FOR JSON Seu aplicativo já troca JSON ou o formato do payload é aninhado ou flexível. Mais portátil para o código do aplicativo, mas geralmente mais lento e menos seguro em relação a tipos do que TVPs ou cópia em massa para inserções estruturadas.

Se você estiver carregando grandes lotes em uma tabela de preparação ou destino, comece com a cópia em massa. Se você estiver invocando procedimentos armazenados com conjuntos de linhas estruturados, comece usando TVPs. Se precisar de documentos aninhados ou esquemas soltos, comece com JSON.

Os exemplos neste artigo usam o banco de dados de exemplo AdventureWorks2025. Exemplos de cópia em massa têm como destino HumanResources.Department e Production.ProductCategory.

Inserção em massa básica

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, elimina as linhas restantes e completa a operação de cópia em massa.

BulkOptions

A mssql.BulkOptions struct 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 Dispare gatilhos INSERT na tabela de destino.
KeepNulls bool Preserve os valores nulos em vez de inserir valores padrão.
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 dica para o índice agrupado alvo (por exemplo, []string{"Id ASC"}).
Tablock bool Adquira um bloqueio em nível de tabela durante a cópia em massa.

Exemplo com opções

Passe BulkOptions para controlar as verificações de restrições, os gatilhos e o bloqueio:

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

Tratamento de erros

Se qualquer linha falhar, toda a operação de cópia em massa falha. Verifique os erros de cada chamada Exec de linha e do flush Exec final:

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
}

Detectar e registrar 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()
}

Dica

Se precisar ignorar linhas com problemas e continuar, use instruções INSERT individuais ou um padrão de tabela de preparação: copie em massa para uma tabela de preparação sem restrições e, em seguida, use um MERGE ou INSERT...SELECT com tratamento de erros para mover os dados para a tabela de destino.

Transmitir a partir de arquivos CSV

Para arquivos CSV grandes, faça streaming de linhas diretamente do arquivo para cópias em massa sem carregar o arquivo 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 grandes cargas de dados. A tabela a seguir mostra características aproximadas de desempenho para inserir 100.000 linhas:

Método Velocidade relativa Viagens de ida e volta na rede Bloqueio
Indivíduo INSERT Mais lento (1x) 100,000 Inserção em nível de linha.
em lote INSERT (1.000 linhas por instrução) Médio (5-10x) 100 Inserção em nível de linha por lote.
Cópia em massa sem TABLOCK Rápido (20-50x) Depende do tamanho do lote Inserção em massa em nível de linha.
Cópia em lote com TABLOCK Mais rápido (50-100x) Depende do tamanho do lote Bloqueio em nível de tabela, registro mínimo.

Note

O desempenho real varia com base na latência da rede, configuração do servidor, índices de tabelas e se há um registro mínimo disponível. Faça benchmarks com sua carga de trabalho específica usando testing.B. Veja o ajuste de desempenho.

Ordenação de colunas e mapeamento de tipos

Colunas em mssql.CopyIn devem corresponder à ordem e aos tipos esperados pela tabela alvo. O driver não realiza correspondência de nomes de coluna; ele usa mapeamento posicional.

Questões de tipos comuns

Tipo Go Coluna do SQL Server Issue Solução
string varchar Conversão nvarchar implícita. Use o wrapper mssql.VarChar.
float64 decimal(18,4) Perda de precisão. Passe como string.
time.Time datetime2 Conversão de fuso horário. Use horários UTC.
nil Qualquer coluna anulável Requer KeepNulls: true. Defina KeepNulls no BulkOptions.

Exemplo com tipos explícitos

Especifique os tipos de colunas explicitamente quando o mapeamento padrão de tipos 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
    }
}

Dicas de desempenho

  • Use Tablock para grandes inserções em tabelas vazias. Esta opção reduz a contenção de bloqueio e permite o registro mínimo.
  • Defina 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 conexão (até 32767) para reduzir a sobrecarga de rede.
  • Ordene os dados para corresponder ao índice agrupado da tabela alvo e defina a Order opção. Essa abordagem evita uma classificação do lado do servidor.
  • Elimine índices não agrupados antes de grandes cargas em massa e depois os reconstrua. A manutenção do índice durante a inserção em massa adiciona sobrecarga.
  • Uso CheckConstraints: false (o padrão) para que dados confiáveis pulem a verificação de restrições durante a cópia em massa.

Limitações

  • Cópia em massa não suporta colunas protegidas pelo Always Encrypted. Para obter mais informações, consulte Limitações.
  • Cópia em massa em nível TDS não é suportada no Banco de Dados SQL do Azure. Para o Banco de Dados SQL do Azure, use instruções em lote INSERT ou um padrão de tabela de preparação.