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
*/
工作原理
- 首先定义一个实现了
sqlhooks.Hooks
接口的结构体,包含Before
和After
方法 - 使用
sqlhooks.Wrap
包装现有的数据库驱动 - 使用
sql.Register
注册包装后的驱动 - 像平常一样使用
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
}
注意事项
- sqlhooks 会为每个查询增加少量开销,生产环境中应谨慎使用性能敏感的钩子
- 钩子中发生的错误会传播到主查询中
- 对于高性能场景,可以考虑使用采样而不是记录所有查询
sqlhooks 是一个强大的工具,可以用于监控、分析、日志记录等多种场景,帮助开发者更好地理解和优化数据库访问模式。