什么!线上数据库频繁全表扫描竟然是因为Gorm?

53 阅读2分钟

问题产生

周五周五,如狼似虎,上帝威武,回家看水浒。什么,线上数据库一直在被全表扫描?美好的周五下午本来已经在等着下班,卷王同事突然告诉我,有个慢查询的问题需要我排查下。

我双手一插,证据呢?没有证据凭什么说是我的问题?卷王同事说sql的源ip全都来自我负责的服务,我定眼一看,还真是!无奈只能转身去排查问题。

问题排查

根据监控发现所有的全表扫描都是由同一个sql语句触发的,这个sql也很简单:select * from table。解决思路也很简单,先找到触发这个sql的代码,然后再看看是什么问题。

得益于服务有良好的架构分层,于是扫了一遍dao层的所有代码,发现没有select * from的代码逻辑。这就很奇怪了,sql确定是从这个服务发出的,源ip也都能对上。没办法只能打一些日志看看了。通过捞日志一顿操作分析,终于定位了问题的代码!幸好是个出现频率很高的问题,大概率可以复现,不然就棘手了。

我们来看看这段有问题的代码:

// GetContentByID 按照 id 获取内容
func GetContentByID(id int64) (po.Content, error) {
   var content po.Content
   if err := MDB.Model(po.Content{}).Where(po.Content{ID: id}).Find(&content).Error; err != nil {
      return po.Content{}, err
   }
   return content, nil
}

关键在于这个Where子句,这里使用结构体的方式作为查询条件的,而gorm不仅在更新的时候会忽略零值的字段,使用结构体作为查询条件时也会忽略零值的字段。

于是,当传入的查询参数id为0(即零值)时,gorm会忽略这个查询字段,导致最后的查询条件为空,触发了数据库的全表扫描。

问题解决

发现了问题之后,解决起来也非常简单,一种修改方式是不使用结构体作为查询条件,修改后的代码如下:

// GetContentByID 按照 id 获取内容
func GetContentByID(id int64) (po.Content, error) {
   var content po.Content
   if err := MDB.Model(po.Content{}).Where("id = ?", id).Find(&content).Error; err != nil {
      return po.Content{}, err
   }
   return content, nil
}

当然也可以选择基于业务逻辑选择提前过滤id为0的情况,或者在上游提前识别零值的查询参数等等,解决方法有很多,选择最契合自身业务场景的就好。

问题解决,背包走人,资本家休想白嫖我🐶。