一个关于 GORM 的奇怪问题

需求

一般在些查询的时候都需要一个简单的 Filer 功能,根据传入的参数做一个系统的筛选

  • Code
//	获取文章分类列表
func GetCategoryList(condition map[string]string) *[]mysql.ArticleCategory {
	category_list := []mysql.ArticleCategory{}
	//	根据传入的参数先筛选一遍数据
	db := filterArticleCategory(condition)

	db.Table("blog_article_group").
		Select("" +
			"blog_article_group.id, " +
			"blog_article_group.name," +
			"count(blog_article.id) AS count, " +
			"blog_article_group.created_at, " +
			"blog_article_group.updated_at").
		Joins("left join blog_article on blog_article_group.id = blog_article.g_id").
		Group("blog_article_group.id").
		Find(&category_list)

	return &category_list
}
//	更具筛选的条件先设置筛选条件
func filterArticleCategory(condition map[string]string) *gorm.DB {
	if _, ok := condition["name"]; ok && condition["name"] != "" {
		db = db.Where("name like ?", "%"+condition["name"]+"%")
	}
	return db
}

查询结果

  • 第一次
curl http://127.0.0.1:9000/article_category

SELECT
	blog_article_group.id,
	blog_article_group.NAME,
	count( blog_article.id ) AS count,
	blog_article_group.created_at,
	blog_article_group.updated_at
FROM
	`blog_article_group`
	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
GROUP BY
	blog_article_group.id
  • 第二次
curl http://127.0.0.1:9000/article_category?name=la

SELECT
	blog_article_group.id,
	blog_article_group.NAME,
	count( blog_article.id ) AS count,
	blog_article_group.created_at,
	blog_article_group.updated_at
FROM
	`blog_article_group`
	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
WHERE
	( NAME LIKE '%la%' )
GROUP BY
	blog_article_group.id
  • 第三次
curl http://127.0.0.1:9000/article_category?name=la

SELECT
	blog_article_group.id,
	blog_article_group.NAME,
	count( blog_article.id ) AS count,
	blog_article_group.created_at,
	blog_article_group.updated_at
FROM
	`blog_article_group`
	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
WHERE
	( NAME LIKE '%la%' )
	AND ( NAME LIKE '%la%' )
GROUP BY
	blog_article_group.id
  • 第四次
curl http://127.0.0.1:9000/article_category?name=la

SELECT
	blog_article_group.id,
	blog_article_group.NAME,
	count( blog_article.id ) AS count,
	blog_article_group.created_at,
	blog_article_group.updated_at
FROM
	`blog_article_group`
	LEFT JOIN blog_article ON blog_article_group.id = blog_article.g_id
WHERE
	( NAME LIKE '%la%' )
	AND ( NAME LIKE '%la%' )
	AND ( NAME LIKE '%la%' )
GROUP BY
	blog_article_group.id

问题分析

在多次查询的时候 filer 函数中的 where 语句会执行多次,最终导致出现多个 AND.

  • 每个请求都是一个 goruntime,其中的 map 也是独立的不会出现冲突
  • GORM 在版本之后返回的 db 都是指针,已经做好了全局的连接池管理

没有看过 GORM 的源码,初步估计是全局返回的 db 指针的问题。

Gorm implements method chaining interface, so you could write code like this:

db, err := gorm.Open("postgres", "user=gorm dbname=gorm sslmode=disable")

// create a new relation
tx := db.Where("name = ?", "jinzhu")

// add more filter
if someCondition {
    tx = tx.Where("age = ?", 20)
} else {
    tx = tx.Where("age = ?", 30)
}

if yetAnotherCondition {
    tx = tx.Where("active = ?", 1)
}

Query won’t be generated until a immediate method, which could be useful in some cases.

Like you could extract a wrapper to handle some common logic

问题

因为使用了全局的 db 指针进行查询,但是内部会共享一个 Scope 导致查询条件重复。官方也给出了方案,就是在操作时需要将全局的 db 复制一份。

func GetCategoryList(condition map[string]string) *[]ArticleCategory {
	category_list := []ArticleCategory{}
	tx := filterArticleCategory(condition)

	tx.Table("blog_article_group").
		Select("" +
			"blog_article_group.id, " +
			"blog_article_group.name," +
			"count(blog_article.id) AS count, " +
			"blog_article_group.created_at, " +
			"blog_article_group.updated_at").
		Joins("left join blog_article on blog_article_group.id = blog_article.g_id").
		Group("blog_article_group.id").
		Find(&category_list)

	return &category_list
}

func filterArticleCategory(condition map[string]string) *gorm.DB {
	tx := db

	if _, ok := condition["name"]; ok && condition["name"] != "" {
		tx = db.Where("name like ?", "%"+condition["name"]+"%")
	}
	return tx
}