golang为database/sql驱动添加钩子功能的插件库sqlhooks的使用

Golang为database/sql驱动添加钩子功能的插件库sqlhooks的使用

sqlhooks是一个可以为任何database/sql驱动添加钩子功能的Golang库。它的主要目的是提供一种方式来检测你的SQL语句,使得在不修改实际代码的情况下,很容易记录查询或测量执行时间。

安装

go get github.com/qustavo/sqlhooks/v2

要求Go版本 >= 1.14.x

使用示例

下面是一个完整的使用示例,展示如何使用sqlhooks来记录SQL查询的执行时间:

// 这个示例展示如何检测SQL查询以显示它们的执行时间
package main

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/qustavo/sqlhooks/v2"
	"github.com/mattn/go-sqlite3"
)

// Hooks 实现 sqlhook.Hooks 接口
type Hooks struct{}

// Before 钩子会打印查询及其参数,并返回带有时间戳的上下文
func (h *Hooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	fmt.Printf("> %s %q", query, args)
	return context.WithValue(ctx, "begin", time.Now()), nil
}

// After 钩子会获取Before钩子中记录的时间戳并打印耗时
func (h *Hooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	begin := ctx.Value("begin").(time.Time)
	fmt.Printf(". took: %s\n", time.Since(begin))
	return ctx, nil
}

func main() {
	// 首先注册包装器
	sql.Register("sqlite3WithHooks", sqlhooks.Wrap(&sqlite3.SQLiteDriver{}, &Hooks{}))

	// 连接到已注册的包装驱动
	db, _ := sql.Open("sqlite3WithHooks", ":memory:")

	// 执行你的操作
	db.Exec("CREATE TABLE t (id INTEGER, text VARCHAR(16))")
	db.Exec("INSERT into t (text) VALUES(?), (?)", "foo", "bar")
	db.Query("SELECT id, text FROM t")
}

/*
输出应该类似这样:
> CREATE TABLE t (id INTEGER, text VARCHAR(16)) []. took: 121.238µs
> INSERT into t (text) VALUES(?), (?) ["foo" "bar"]. took: 36.364µs
> SELECT id, text FROM t []. took: 4.653µs
*/

工作原理

  1. 首先定义一个实现了sqlhooks.Hooks接口的结构体,包含BeforeAfter方法
  2. 使用sqlhooks.Wrap包装现有的数据库驱动
  3. 使用sql.Register注册包装后的驱动
  4. 像平常一样使用database/sql包操作数据库

性能基准

根据基准测试,添加钩子对性能的影响很小:

go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: github.com/qustavo/sqlhooks/v2
cpu: Intel(R) Xeon(R) W-10885M CPU @ 2.40GHz
BenchmarkSQLite3/Without_Hooks-16                 191196              6163 ns/op             456 B/op         14 allocs/op
BenchmarkSQLite3/With_Hooks-16                    189997              6329 ns/op             456 B/op         14 allocs/op
BenchmarkMySQL/Without_Hooks-16                    13278             83462 ns/op             309 B/op          7 allocs/op
BenchmarkMySQL/With_Hooks-16                       13460             87331 ns/op             309 B/op          7 allocs/op
BenchmarkPostgres/Without_Hooks-16                 13016             91421 ns/op             401 B/op         10 allocs/op
BenchmarkPostgres/With_Hooks-16                    12339             94033 ns/op             401 B/op         10 allocs/op
PASS
ok      github.com/qustavo/sqlhooks/v2  10.294s

这个库非常适合需要监控SQL查询性能或记录查询日志的场景,而对性能的影响可以忽略不计。


更多关于golang为database/sql驱动添加钩子功能的插件库sqlhooks的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang为database/sql驱动添加钩子功能的插件库sqlhooks的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang sqlhooks 使用指南

sqlhooks 是一个为 Go 的 database/sql 包添加钩子功能的插件库,它允许你在 SQL 查询执行前后插入自定义逻辑。

安装

go get github.com/gchaincl/sqlhooks

基本用法

1. 创建钩子

首先定义实现 sqlhooks.Hooks 接口的结构体:

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/gchaincl/sqlhooks"
	_ "github.com/go-sql-driver/mysql"
)

// 定义钩子结构体
type MyHooks struct{}

// 实现 Before 方法
func (h *MyHooks) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	fmt.Printf("Before> query: %s, args: %v\n", query, args)
	// 可以在上下文中存储开始时间
	return context.WithValue(ctx, "start", time.Now()), nil
}

// 实现 After 方法
func (h *MyHooks) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	// 从上下文中获取开始时间
	start := ctx.Value("start").(time.Time)
	duration := time.Since(start)
	fmt.Printf("After> query: %s, took: %v\n", query, duration)
	return ctx, nil
}

2. 注册驱动并连接数据库

func main() {
	// 注册带有钩子的驱动
	sql.Register("mysql-with-hooks", sqlhooks.Wrap(&mysql.MySQLDriver{}, &MyHooks{}))

	// 使用带钩子的驱动连接数据库
	db, err := sql.Open("mysql-with-hooks", "user:password@/dbname")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	// 执行查询
	rows, err := db.Query("SELECT * FROM users WHERE id = ?", 1)
	if err != nil {
		panic(err)
	}
	defer rows.Close()

	// 处理结果...
}

高级用法

1. 记录慢查询

type SlowQueryHook struct {
	Threshold time.Duration
}

func (h *SlowQueryHook) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	return context.WithValue(ctx, "start", time.Now()), nil
}

func (h *SlowQueryHook) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	start := ctx.Value("start").(time.Time)
	duration := time.Since(start)
	
	if duration > h.Threshold {
		fmt.Printf("SLOW QUERY: %s (%v)\nArgs: %v\n", query, duration, args)
	}
	return ctx, nil
}

// 使用
sql.Register("mysql-slow-query", sqlhooks.Wrap(&mysql.MySQLDriver{}, &SlowQueryHook{
	Threshold: 100 * time.Millisecond,
}))

2. SQL 日志记录

type SQLLogger struct{}

func (l *SQLLogger) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	log.Printf("Executing: %s\nWith args: %v", query, args)
	return ctx, nil
}

func (l *SQLLogger) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	return ctx, nil
}

3. SQL 分析

type SQLAnalyzer struct {
	stats map[string]time.Duration
	count map[string]int
}

func NewSQLAnalyzer() *SQLAnalyzer {
	return &SQLAnalyzer{
		stats: make(map[string]time.Duration),
		count: make(map[string]int),
	}
}

func (a *SQLAnalyzer) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	return context.WithValue(ctx, "start", time.Now()), nil
}

func (a *SQLAnalyzer) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
	start := ctx.Value("start").(time.Time)
	duration := time.Since(start)
	
	// 简化查询作为key
	key := simplifyQuery(query)
	
	a.stats[key] += duration
	a.count[key]++
	
	return ctx, nil
}

func (a *SQLAnalyzer) Report() {
	fmt.Println("SQL Query Analysis Report:")
	for query, total := range a.stats {
		avg := total / time.Duration(a.count[query])
		fmt.Printf("- %s: %d calls, total %v, avg %v\n", 
			query, a.count[query], total, avg)
	}
}

func simplifyQuery(query string) string {
	// 简化查询逻辑,去除具体参数值等
	return query
}

注意事项

  1. sqlhooks 会为每个查询增加少量开销,生产环境中应谨慎使用性能敏感的钩子
  2. 钩子中发生的错误会传播到主查询中
  3. 对于高性能场景,可以考虑使用采样而不是记录所有查询

sqlhooks 是一个强大的工具,可以用于监控、分析、日志记录等多种场景,帮助开发者更好地理解和优化数据库访问模式。

回到顶部