惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
N
Netflix TechBlog - Medium
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
J
Java Code Geeks
V
Visual Studio Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
Hugging Face - Blog
Hugging Face - Blog
Microsoft Security Blog
Microsoft Security Blog
腾讯CDC
博客园 - 【当耐特】
B
Blog
Stack Overflow Blog
Stack Overflow Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
GbyAI
GbyAI
博客园 - 司徒正美
博客园 - 叶小钗
Y
Y Combinator Blog
MyScale Blog
MyScale Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
G
Google Developers Blog
酷 壳 – CoolShell
酷 壳 – CoolShell

流动

山西之旅 | 流动 博客焕新,记录继续 | 流动 友链 | 流动 博客加速实践 广府古城一日游 我做了一个口播短视频二创工具:VideoRemaker Windows 下 Codex Cli 更新报 Move-Item is denied 错误的解决 Caddy 转发 GOST 报 TLS internal error 问题的排查与解决 对口型视频合成方案对比:Wav2Lip、VideoReTalking 与 MuseTalk 2026 音色克隆方案对比:IndexTTS-2、CosyVoice、GPT-SoVITS、Fish Speech、VoxCPM 部署与实测 AI Agent折腾记(OpenClaw / Hermes Agent) 我的2025年 大连之行 回家收麦 六一儿童节爬长城 Golang database/sql 数据库断线自动重连机制解析 Golang默认Http Client导致的cannot assign requested address错误 清明踏春,爬山看海 购入小牛G400T电动车 北京的三月飞雪 wrenAI本地LLM模型部署 天津一日游 2024年终总结 停止使用staticfile.org服务 使用 ImageMagick 自动添加水印,保护图片版权 如何注册一个.sol域名 奥森公园半日游 昌平42公里骑行绿道打卡 十月一日爬慕田峪长城 当Hugo遇上AVIF,优化图片加载
GORM增加sqlcommenter特性
Liudon · 2024-04-18 · via 流动

什么是sqlcommenter?

sqlcommenter is a suite of middlewares/plugins that enable your ORMs to augment SQL statements before execution, with comments containing information about the code that caused its execution. This helps in easily correlating slow performance with source code and giving insights into backend database performance. In short it provides some observability into the state of your client-side applications and their impact on the database’s server-side.

GORM提供了hints组件,可以支持sqlcommenter

import "gorm.io/hints"

DB.Clauses(hints.Comment("select", "master")).Find(&User{})
// SELECT /*master*/ * FROM `users`;

DB.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
// /*node2*/ INSERT INTO `users` ...;

DB.Clauses(hints.CommentAfter("select", "node2")).Create(&user)
// /*node2*/ INSERT INTO `users` ...;

DB.Clauses(hints.CommentAfter("where", "hint")).Find(&User{}, "id = ?", 1)
// SELECT * FROM `users` WHERE id = ? /* hint */

但是需要在每个执行语句里引入类似.Clauses(hints.CommentBefore("insert", "node2"))代码。

我希望是全局增加sqlcommenter,业务侧不需要过多调整。

完整代码如下:

plugins/gorm.go

package plugins

import (
	"fmt"

	gorm "gorm.io/gorm"
	gormclause "gorm.io/gorm/clause"
)

type Comment struct {
	Content string
}

func (c Comment) Name() string {
	return "COMMENT"
}

func (c Comment) Build(builder gormclause.Builder) {
	builder.WriteString("/* ")
	builder.WriteString(c.Content)
	builder.WriteString(" */")
}

func (c Comment) MergeClause(mergeClause *gormclause.Clause) {
}

func (c Comment) ModifyStatement(stmt *gorm.Statement) {
	clause := stmt.Clauses[c.Name()]
    // 注意这里一定要是Expression,因为Expression为nil的话,是不会触发Build方法执行的
    // 这里一开始参考hints注册的BeforeExpression,导致Build未执行,直到把整个gorm流程梳理一遍才发现问题所在
	clause.Expression = c
	stmt.Clauses[c.Name()] = clause
}

var extraClause = []string{"COMMENT"}

type CommentClausePlugin struct{}

// NewCommentClausePlugin create a new ExtraPlugin
func NewCommentClausePlugin() *CommentClausePlugin {
	return &CommentClausePlugin{}
}

// Name plugin name
func (ep *CommentClausePlugin) Name() string {
	return "CommentClausePlugin"
}

// Initialize register BuildClauses
func (ep *CommentClausePlugin) Initialize(db *gorm.DB) (err error) {
	initClauses(db)
	db.Callback().Create().Before("gorm:create").Register("CommentClausePlugin", AddAnnotation)
	db.Callback().Delete().Before("gorm:delete").Register("CommentClausePlugin", AddAnnotation)
	db.Callback().Query().Before("gorm:query").Register("CommentClausePlugin", AddAnnotation)
	db.Callback().Update().Before("gorm:update").Register("CommentClausePlugin", AddAnnotation)
	db.Callback().Raw().Before("gorm:raw").Register("CommentClausePlugin", AddAnnotation)
	db.Callback().Row().Before("gorm:row").Register("CommentClausePlugin", AddAnnotation)

	return
}

func AddAnnotation(db *gorm.DB) {
	if db.Error != nil {
		return
	}

	rid := "xx"
	// context上下文里取rid信息
	if v, ok := db.Statement.Context.Value("rid").(string); ok {
		rid = v
	}

	content := fmt.Sprintf("rid=%s", rid)

	if db.Statement.SQL.Len() > 0 {
		oldSQL := db.Statement.SQL.String()
		db.Statement.SQL.Reset()
		db.Statement.SQL.WriteString(fmt.Sprintf("%s %s", content, oldSQL))
		return
	}

	db.Statement.AddClause(Comment{Content: content})
}

// initClauses init SQL clause
func initClauses(db *gorm.DB) {
	if db.Error != nil {
		return
	}
	createClause := append(extraClause, db.Callback().Create().Clauses...)
	deleteClause := append(extraClause, db.Callback().Delete().Clauses...)
	queryClause := append(extraClause, db.Callback().Query().Clauses...)
	updateClause := append(extraClause, db.Callback().Update().Clauses...)
	rawClause := append(extraClause, db.Callback().Raw().Clauses...)
	rowClause := append(extraClause, db.Callback().Row().Clauses...)
	db.Callback().Create().Clauses = createClause
	db.Callback().Delete().Clauses = deleteClause
	db.Statement.Callback().Query().Clauses = queryClause
	db.Callback().Update().Clauses = updateClause
	db.Callback().Raw().Clauses = rawClause
	db.Callback().Row().Clauses = rowClause
}


main.go
package main

import (
    "context"
    "plugins"

    gorm "gorm.io/gorm"
    "github.com/google/uuid"
)


type Product struct {
  gorm.Model
  Code  string
  Price uint
}

func main() {
    dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

    db.Use(plugins.NewCommentClausePlugin())

    db.Create(&Product{Code: "D42", Price: 100})

    // 传入context,指定rid
    ctx := context.WithValue(context.Background(), "rid", uuid.New().String())
    db.WithContext(ctx).Create(&Product{Code: "D42", Price: 100})
}

阻塞了两天的问题,终于解决了!😁😁😁

how gorm generates sql