374 lines
7.8 KiB
Go
374 lines
7.8 KiB
Go
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||
|
//
|
||
|
// Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved.
|
||
|
//
|
||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
|
|
||
|
package mysql
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"context"
|
||
|
"database/sql"
|
||
|
"database/sql/driver"
|
||
|
"fmt"
|
||
|
"math"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"testing"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type TB testing.B
|
||
|
|
||
|
func (tb *TB) check(err error) {
|
||
|
if err != nil {
|
||
|
tb.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (tb *TB) checkDB(db *sql.DB, err error) *sql.DB {
|
||
|
tb.check(err)
|
||
|
return db
|
||
|
}
|
||
|
|
||
|
func (tb *TB) checkRows(rows *sql.Rows, err error) *sql.Rows {
|
||
|
tb.check(err)
|
||
|
return rows
|
||
|
}
|
||
|
|
||
|
func (tb *TB) checkStmt(stmt *sql.Stmt, err error) *sql.Stmt {
|
||
|
tb.check(err)
|
||
|
return stmt
|
||
|
}
|
||
|
|
||
|
func initDB(b *testing.B, queries ...string) *sql.DB {
|
||
|
tb := (*TB)(b)
|
||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||
|
for _, query := range queries {
|
||
|
if _, err := db.Exec(query); err != nil {
|
||
|
b.Fatalf("error on %q: %v", query, err)
|
||
|
}
|
||
|
}
|
||
|
return db
|
||
|
}
|
||
|
|
||
|
const concurrencyLevel = 10
|
||
|
|
||
|
func BenchmarkQuery(b *testing.B) {
|
||
|
tb := (*TB)(b)
|
||
|
b.StopTimer()
|
||
|
b.ReportAllocs()
|
||
|
db := initDB(b,
|
||
|
"DROP TABLE IF EXISTS foo",
|
||
|
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
|
||
|
`INSERT INTO foo VALUES (1, "one")`,
|
||
|
`INSERT INTO foo VALUES (2, "two")`,
|
||
|
)
|
||
|
db.SetMaxIdleConns(concurrencyLevel)
|
||
|
defer db.Close()
|
||
|
|
||
|
stmt := tb.checkStmt(db.Prepare("SELECT val FROM foo WHERE id=?"))
|
||
|
defer stmt.Close()
|
||
|
|
||
|
remain := int64(b.N)
|
||
|
var wg sync.WaitGroup
|
||
|
wg.Add(concurrencyLevel)
|
||
|
defer wg.Wait()
|
||
|
b.StartTimer()
|
||
|
|
||
|
for i := 0; i < concurrencyLevel; i++ {
|
||
|
go func() {
|
||
|
for {
|
||
|
if atomic.AddInt64(&remain, -1) < 0 {
|
||
|
wg.Done()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var got string
|
||
|
tb.check(stmt.QueryRow(1).Scan(&got))
|
||
|
if got != "one" {
|
||
|
b.Errorf("query = %q; want one", got)
|
||
|
wg.Done()
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkExec(b *testing.B) {
|
||
|
tb := (*TB)(b)
|
||
|
b.StopTimer()
|
||
|
b.ReportAllocs()
|
||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||
|
db.SetMaxIdleConns(concurrencyLevel)
|
||
|
defer db.Close()
|
||
|
|
||
|
stmt := tb.checkStmt(db.Prepare("DO 1"))
|
||
|
defer stmt.Close()
|
||
|
|
||
|
remain := int64(b.N)
|
||
|
var wg sync.WaitGroup
|
||
|
wg.Add(concurrencyLevel)
|
||
|
defer wg.Wait()
|
||
|
b.StartTimer()
|
||
|
|
||
|
for i := 0; i < concurrencyLevel; i++ {
|
||
|
go func() {
|
||
|
for {
|
||
|
if atomic.AddInt64(&remain, -1) < 0 {
|
||
|
wg.Done()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if _, err := stmt.Exec(); err != nil {
|
||
|
b.Fatal(err.Error())
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// data, but no db writes
|
||
|
var roundtripSample []byte
|
||
|
|
||
|
func initRoundtripBenchmarks() ([]byte, int, int) {
|
||
|
if roundtripSample == nil {
|
||
|
roundtripSample = []byte(strings.Repeat("0123456789abcdef", 1024*1024))
|
||
|
}
|
||
|
return roundtripSample, 16, len(roundtripSample)
|
||
|
}
|
||
|
|
||
|
func BenchmarkRoundtripTxt(b *testing.B) {
|
||
|
b.StopTimer()
|
||
|
sample, min, max := initRoundtripBenchmarks()
|
||
|
sampleString := string(sample)
|
||
|
b.ReportAllocs()
|
||
|
tb := (*TB)(b)
|
||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||
|
defer db.Close()
|
||
|
b.StartTimer()
|
||
|
var result string
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
length := min + i
|
||
|
if length > max {
|
||
|
length = max
|
||
|
}
|
||
|
test := sampleString[0:length]
|
||
|
rows := tb.checkRows(db.Query(`SELECT "` + test + `"`))
|
||
|
if !rows.Next() {
|
||
|
rows.Close()
|
||
|
b.Fatalf("crashed")
|
||
|
}
|
||
|
err := rows.Scan(&result)
|
||
|
if err != nil {
|
||
|
rows.Close()
|
||
|
b.Fatalf("crashed")
|
||
|
}
|
||
|
if result != test {
|
||
|
rows.Close()
|
||
|
b.Errorf("mismatch")
|
||
|
}
|
||
|
rows.Close()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkRoundtripBin(b *testing.B) {
|
||
|
b.StopTimer()
|
||
|
sample, min, max := initRoundtripBenchmarks()
|
||
|
b.ReportAllocs()
|
||
|
tb := (*TB)(b)
|
||
|
db := tb.checkDB(sql.Open("mysql", dsn))
|
||
|
defer db.Close()
|
||
|
stmt := tb.checkStmt(db.Prepare("SELECT ?"))
|
||
|
defer stmt.Close()
|
||
|
b.StartTimer()
|
||
|
var result sql.RawBytes
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
length := min + i
|
||
|
if length > max {
|
||
|
length = max
|
||
|
}
|
||
|
test := sample[0:length]
|
||
|
rows := tb.checkRows(stmt.Query(test))
|
||
|
if !rows.Next() {
|
||
|
rows.Close()
|
||
|
b.Fatalf("crashed")
|
||
|
}
|
||
|
err := rows.Scan(&result)
|
||
|
if err != nil {
|
||
|
rows.Close()
|
||
|
b.Fatalf("crashed")
|
||
|
}
|
||
|
if !bytes.Equal(result, test) {
|
||
|
rows.Close()
|
||
|
b.Errorf("mismatch")
|
||
|
}
|
||
|
rows.Close()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func BenchmarkInterpolation(b *testing.B) {
|
||
|
mc := &mysqlConn{
|
||
|
cfg: &Config{
|
||
|
InterpolateParams: true,
|
||
|
Loc: time.UTC,
|
||
|
},
|
||
|
maxAllowedPacket: maxPacketSize,
|
||
|
maxWriteSize: maxPacketSize - 1,
|
||
|
buf: newBuffer(nil),
|
||
|
}
|
||
|
|
||
|
args := []driver.Value{
|
||
|
int64(42424242),
|
||
|
float64(math.Pi),
|
||
|
false,
|
||
|
time.Unix(1423411542, 807015000),
|
||
|
[]byte("bytes containing special chars ' \" \a \x00"),
|
||
|
"string containing special chars ' \" \a \x00",
|
||
|
}
|
||
|
q := "SELECT ?, ?, ?, ?, ?, ?"
|
||
|
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
for i := 0; i < b.N; i++ {
|
||
|
_, err := mc.interpolateParams(q, args)
|
||
|
if err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func benchmarkQueryContext(b *testing.B, db *sql.DB, p int) {
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
defer cancel()
|
||
|
db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
|
||
|
|
||
|
tb := (*TB)(b)
|
||
|
stmt := tb.checkStmt(db.PrepareContext(ctx, "SELECT val FROM foo WHERE id=?"))
|
||
|
defer stmt.Close()
|
||
|
|
||
|
b.SetParallelism(p)
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
b.RunParallel(func(pb *testing.PB) {
|
||
|
var got string
|
||
|
for pb.Next() {
|
||
|
tb.check(stmt.QueryRow(1).Scan(&got))
|
||
|
if got != "one" {
|
||
|
b.Fatalf("query = %q; want one", got)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func BenchmarkQueryContext(b *testing.B) {
|
||
|
db := initDB(b,
|
||
|
"DROP TABLE IF EXISTS foo",
|
||
|
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
|
||
|
`INSERT INTO foo VALUES (1, "one")`,
|
||
|
`INSERT INTO foo VALUES (2, "two")`,
|
||
|
)
|
||
|
defer db.Close()
|
||
|
for _, p := range []int{1, 2, 3, 4} {
|
||
|
b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
|
||
|
benchmarkQueryContext(b, db, p)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func benchmarkExecContext(b *testing.B, db *sql.DB, p int) {
|
||
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
defer cancel()
|
||
|
db.SetMaxIdleConns(p * runtime.GOMAXPROCS(0))
|
||
|
|
||
|
tb := (*TB)(b)
|
||
|
stmt := tb.checkStmt(db.PrepareContext(ctx, "DO 1"))
|
||
|
defer stmt.Close()
|
||
|
|
||
|
b.SetParallelism(p)
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
b.RunParallel(func(pb *testing.PB) {
|
||
|
for pb.Next() {
|
||
|
if _, err := stmt.ExecContext(ctx); err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func BenchmarkExecContext(b *testing.B) {
|
||
|
db := initDB(b,
|
||
|
"DROP TABLE IF EXISTS foo",
|
||
|
"CREATE TABLE foo (id INT PRIMARY KEY, val CHAR(50))",
|
||
|
`INSERT INTO foo VALUES (1, "one")`,
|
||
|
`INSERT INTO foo VALUES (2, "two")`,
|
||
|
)
|
||
|
defer db.Close()
|
||
|
for _, p := range []int{1, 2, 3, 4} {
|
||
|
b.Run(fmt.Sprintf("%d", p), func(b *testing.B) {
|
||
|
benchmarkQueryContext(b, db, p)
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// BenchmarkQueryRawBytes benchmarks fetching 100 blobs using sql.RawBytes.
|
||
|
// "size=" means size of each blobs.
|
||
|
func BenchmarkQueryRawBytes(b *testing.B) {
|
||
|
var sizes []int = []int{100, 1000, 2000, 4000, 8000, 12000, 16000, 32000, 64000, 256000}
|
||
|
db := initDB(b,
|
||
|
"DROP TABLE IF EXISTS bench_rawbytes",
|
||
|
"CREATE TABLE bench_rawbytes (id INT PRIMARY KEY, val LONGBLOB)",
|
||
|
)
|
||
|
defer db.Close()
|
||
|
|
||
|
blob := make([]byte, sizes[len(sizes)-1])
|
||
|
for i := range blob {
|
||
|
blob[i] = 42
|
||
|
}
|
||
|
for i := 0; i < 100; i++ {
|
||
|
_, err := db.Exec("INSERT INTO bench_rawbytes VALUES (?, ?)", i, blob)
|
||
|
if err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, s := range sizes {
|
||
|
b.Run(fmt.Sprintf("size=%v", s), func(b *testing.B) {
|
||
|
db.SetMaxIdleConns(0)
|
||
|
db.SetMaxIdleConns(1)
|
||
|
b.ReportAllocs()
|
||
|
b.ResetTimer()
|
||
|
|
||
|
for j := 0; j < b.N; j++ {
|
||
|
rows, err := db.Query("SELECT LEFT(val, ?) as v FROM bench_rawbytes", s)
|
||
|
if err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
nrows := 0
|
||
|
for rows.Next() {
|
||
|
var buf sql.RawBytes
|
||
|
err := rows.Scan(&buf)
|
||
|
if err != nil {
|
||
|
b.Fatal(err)
|
||
|
}
|
||
|
if len(buf) != s {
|
||
|
b.Fatalf("size mismatch: expected %v, got %v", s, len(buf))
|
||
|
}
|
||
|
nrows++
|
||
|
}
|
||
|
rows.Close()
|
||
|
if nrows != 100 {
|
||
|
b.Fatalf("numbers of rows mismatch: expected %v, got %v", 100, nrows)
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|