postgres batch insert

inserting lots of values into postgres from go

SEAN K.H. LIAO

postgres batch insert

inserting lots of values into postgres from go

inserting lots of values into postgres

So how do you insert a bunch of values into a postgres database? You could call insert a bunch of times. You could do the same thing but in a transaction. There's also the unnest function (which is sadly not portable to sqlite), or there's the magic CopyFrom function which goes zoom.

 1package main
 2
 3import (
 4        "context"
 5        "crypto/rand"
 6        "encoding/hex"
 7        mathrand "math/rand"
 8        "os"
 9        "testing"
10
11        "github.com/jackc/pgx/v5"
12)
13
14func BenchmarkBatchInsert(b *testing.B) {
15        dataStrings := make([]string, 1048576)
16        dataInt := make([]int, 1048576)
17        for i := 0; i < len(dataStrings); i++ {
18                dataInt[i] = mathrand.Int()
19                buf := make([]byte, 16)
20                rand.Read(buf)
21                dataStrings[i] = hex.EncodeToString(buf)
22        }
23
24        b.Run("separate", func(b *testing.B) {
25                ctx, conn := getConn("separate")
26                b.ResetTimer()
27
28                for i := 0; i < b.N; i++ {
29                        _, err := conn.Exec(ctx, `insert into separate (a, b) values ($1, $2)`, dataStrings[i], dataInt[i])
30                        if err != nil {
31                                panic(err)
32                        }
33                }
34        })
35        b.Run("single_tx", func(b *testing.B) {
36                ctx, conn := getConn("single_tx")
37                b.ResetTimer()
38
39                tx, err := conn.BeginTx(ctx, pgx.TxOptions{})
40                if err != nil {
41                        panic(err)
42                }
43                defer tx.Commit(ctx)
44
45                for i := 0; i < b.N; i++ {
46                        _, err := tx.Exec(ctx, `insert into single_tx (a, b) values ($1, $2)`, dataStrings[i], dataInt[i])
47                        if err != nil {
48                                panic(err)
49                        }
50                }
51        })
52        b.Run("unnest", func(b *testing.B) {
53                ctx, conn := getConn("unnest")
54                b.ResetTimer()
55                _, err := conn.Exec(ctx, `insert into unnest select * from unnest($1::text[], $2::numeric[]);`, dataStrings[:b.N], dataInt[:b.N])
56                if err != nil {
57                        panic(err)
58                }
59        })
60        b.Run("copy", func(b *testing.B) {
61                ctx, conn := getConn("copy")
62
63                rows := make([][]any, 0, len(dataStrings))
64                for i := 0; i < len(dataStrings); i++ {
65                        rows = append(rows, []any{dataStrings[i], dataInt[i]})
66                }
67
68                b.ResetTimer()
69                _, err := conn.CopyFrom(ctx, pgx.Identifier{"copy"}, []string{"a", "b"}, pgx.CopyFromRows(rows[:b.N]))
70                if err != nil {
71                        panic(err)
72                }
73        })
74}
75
76func getConn(name string) (context.Context, *pgx.Conn) {
77        ctx := context.Background()
78        conn, err := pgx.Connect(ctx, os.Getenv("DATABASE_URL"))
79        if err != nil {
80                panic(err)
81        }
82
83        _, err = conn.Exec(ctx, `drop table if exists `+name+`;`)
84        if err != nil {
85                panic(err)
86        }
87        _, err = conn.Exec(ctx, `create table `+name+` ( a text, b numeric);`)
88        if err != nil {
89                panic(err)
90        }
91
92        return ctx, conn
93}

Benchmark results:

 1$ go test -bench=. -benchtime=100000x
 2goos: linux
 3goarch: amd64
 4pkg: go.seankhliao.com/testrepo0482
 5cpu: 12th Gen Intel(R) Core(TM) i7-1260P
 6BenchmarkBatchInsert/separate-16                  100000           253591 ns/op
 7BenchmarkBatchInsert/single_tx-16                 100000            26578 ns/op
 8BenchmarkBatchInsert/unnest-16                    100000             1267 ns/op
 9BenchmarkBatchInsert/copy-16                      100000              352.2 ns/op
10PASS
11ok          go.seankhliao.com/testrepo0482        28.838s