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.