Reusing SQL Prepared Statements in Go
28 Apr 2019
How much time is saved by re-using prepared statements in Go?
This simple experiment will give you a rough idea.
Background knowledge: Go's sql package uses prepared
statements when filling in parameters in SQL queries.
So this snippet of code:
err = db.QueryRow("select count(*) from pg_stat_activity where datname = $1", "postgres").Scan(&num)
will show the use of a prepared statement in PostgreSQL's logs if you have statement logging turned on:
LOG: execute: select count(*) from pg_stat_activity where datname = $1 DETAIL: parameters: $1 = '42'
Let's run the above basic query 1000 times using this program:
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "postgres://postgres:postgres@localhost/postgres?sslmode=disable")
if err != nil {
log.Fatal(err)
}
var num int
start := time.Now()
for i := 0; i < 1000; i++ {
err = db.QueryRow("select count(*) from pg_stat_activity where datname = $1", "postgres").Scan(&num)
if err != nil {
log.Fatal(err)
}
}
elapsed := time.Since(start)
fmt.Printf("got: %d in %s\n", num, elapsed)
}
Here's what we get with 5 runs of the above program:
got: 1 in 411.181744ms got: 1 in 421.445645ms got: 1 in 452.958473ms got: 1 in 419.599104ms got: 1 in 432.694446msNow let's try a slightly more sophisticated program, where we prepare the statement up front, and re-use it 1000 times instead:
package main
import (
"database/sql"
"fmt"
"log"
"time"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "postgres://postgres:postgres@localhost/postgres?sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
stmt, err := db.Prepare("select count(*) from pg_stat_activity where datname = $1")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
var num int
start := time.Now()
for i := 0; i < 1000; i++ {
err = stmt.QueryRow("postgres").Scan(&num)
}
elapsed := time.Since(start)
fmt.Printf("got: %d in %s\n", num, elapsed)
}
We get query times that are about three and a half times faster:
got: 1 in 115.087555ms got: 1 in 121.813083ms got: 1 in 121.280645ms got: 1 in 122.50746ms got: 1 in 125.474026ms
There are obviously many more sophisticated ways we could look at this, but as a rough guide to see if prepared statements are worth caching, it looks like it's a good practice.