一个关于 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
}