How do I batch sql statements with package database/sql

前端 未结 12 2649
深忆病人
深忆病人 2020-12-04 16:46

How do I batch sql statements with Go\'s database/sql package?

In Java I would do it like this :

// Create a prepared statement
String sql = \"INSERT         


        
相关标签:
12条回答
  • 2020-12-04 17:10

    In case anyone is using pgx (the supposed best Postgres driver in Golang), see this solution: https://github.com/jackc/pgx/issues/764#issuecomment-685249471

    0 讨论(0)
  • 2020-12-04 17:11

    Batching is not possible via the interfaces available in database/sql. A particular database driver may support it separately, however. For instance https://github.com/ziutek/mymysql appears to support batching with MySQL.

    0 讨论(0)
  • 2020-12-04 17:16

    Since the db.Exec function is variadic, one option (that actually does only make a single network roundtrip) is to construct the statement yourself and explode the arguments and pass them in.

    Sample code:

    func BulkInsert(unsavedRows []*ExampleRowStruct) error {
        valueStrings := make([]string, 0, len(unsavedRows))
        valueArgs := make([]interface{}, 0, len(unsavedRows) * 3)
        for _, post := range unsavedRows {
            valueStrings = append(valueStrings, "(?, ?, ?)")
            valueArgs = append(valueArgs, post.Column1)
            valueArgs = append(valueArgs, post.Column2)
            valueArgs = append(valueArgs, post.Column3)
        }
        stmt := fmt.Sprintf("INSERT INTO my_sample_table (column1, column2, column3) VALUES %s", 
                            strings.Join(valueStrings, ","))
        _, err := db.Exec(stmt, valueArgs...)
        return err
    }
    

    In a simple test I ran, this solution is about 4 times faster at inserting 10,000 rows than the Begin, Prepare, Commit presented in the other answer - though the actual improvement will depend a lot on your individual setup, network latencies, etc.

    0 讨论(0)
  • 2020-12-04 17:17

    If you’re using PostgreSQL then pq supports bulk imports.

    0 讨论(0)
  • 2020-12-04 17:17

    Adapting Andrew's solution for PostgreSQL, which doesn't support the ? placeholder, the following works:

    func BulkInsert(unsavedRows []*ExampleRowStruct) error {
        valueStrings := make([]string, 0, len(unsavedRows))
        valueArgs := make([]interface{}, 0, len(unsavedRows) * 3)
        i := 0
        for _, post := range unsavedRows {
            valueStrings = append(valueStrings, fmt.Sprintf("($%d, $%d, $%d)", i*3+1, i*3+2, i*3+3))
            valueArgs = append(valueArgs, post.Column1)
            valueArgs = append(valueArgs, post.Column2)
            valueArgs = append(valueArgs, post.Column3)
            i++
        }
        stmt := fmt.Sprintf("INSERT INTO my_sample_table (column1, column2, column3) VALUES %s", strings.Join(valueStrings, ","))
        _, err := db.Exec(stmt, valueArgs...)
        return err
    }
    
    0 讨论(0)
  • 2020-12-04 17:18

    Here's a more generic version to generate the query & value args based on answers from @andrew-c and @mastercarl:

    // bulk/insert.go

    import (
        "strconv"
        "strings"
    )
    
    type ValueExtractor = func(int) []interface{}
    
    func Generate(tableName string, columns []string, numRows int, postgres bool, valueExtractor ValueExtractor) (string, []interface{}) {
        numCols := len(columns)
        var queryBuilder strings.Builder
        queryBuilder.WriteString("INSERT INTO ")
        queryBuilder.WriteString(tableName)
        queryBuilder.WriteString("(")
        for i, column := range columns {
            queryBuilder.WriteString("\"")
            queryBuilder.WriteString(column)
            queryBuilder.WriteString("\"")
            if i < numCols-1 {
                queryBuilder.WriteString(",")
            }
        }
        queryBuilder.WriteString(") VALUES ")
        var valueArgs []interface{}
        valueArgs = make([]interface{}, 0, numRows*numCols)
        for rowIndex := 0; rowIndex < numRows; rowIndex++ {
            queryBuilder.WriteString("(")
            for colIndex := 0; colIndex < numCols; colIndex++ {
                if postgres {
                    queryBuilder.WriteString("$")
                    queryBuilder.WriteString(strconv.Itoa(rowIndex*numCols + colIndex + 1))
                } else {
                    queryBuilder.WriteString("?")
                }
                if colIndex < numCols-1 {
                    queryBuilder.WriteString(",")
                }
            }
            queryBuilder.WriteString(")")
            if rowIndex < numRows-1 {
                queryBuilder.WriteString(",")
            }
            valueArgs = append(valueArgs, valueExtractor(rowIndex)...)
        }
        return queryBuilder.String(), valueArgs
    }
    

    // bulk/insert_test.go

    import (
        "fmt"
        "strconv"
    )
    
    func valueExtractor(index int) []interface{} {
        return []interface{}{
            "trx-" + strconv.Itoa(index),
            "name-" + strconv.Itoa(index),
            index,
        }
    }
    
    func ExampleGeneratePostgres() {
        query, valueArgs := Generate("tbl_persons", []string{"transaction_id", "name", "age"}, 3, true, valueExtractor)
        fmt.Println(query)
        fmt.Println(valueArgs)
        // Output:
        // INSERT INTO tbl_persons("transaction_id","name","age") VALUES ($1,$2,$3),($4,$5,$6),($7,$8,$9)
        // [[trx-0 name-0 0] [trx-1 name-1 1] [trx-2 name-2 2]]
    }
    
    func ExampleGenerateOthers() {
        query, valueArgs := Generate("tbl_persons", []string{"transaction_id", "name", "age"}, 3, false, valueExtractor)
        fmt.Println(query)
        fmt.Println(valueArgs)
        // Output:
        // INSERT INTO tbl_persons("transaction_id","name","age") VALUES (?,?,?),(?,?,?),(?,?,?)
        // [[trx-0 name-0 0] [trx-1 name-1 1] [trx-2 name-2 2]]
    }
    
    0 讨论(0)
提交回复
热议问题