误用gorm引起的一宗血案

现象

某服务A从晚上6点开始,监控一直出现mysql调用延迟超高,http接口500飙升

原因

经查发现是一条全局update整张表的sql引起的,全局lock表

最小化

使用下面操作进行最小化

建库 create databses test;
建表 CREATE TABLE `Record` ( `id`  BIGINT NOT NULL, `count` BIGINT NOT NULL, PRIMARY KEY(`id`) ) ENGINE = InnoDB;
插入一条数据 CREATE TABLE `Record` ( `id`  BIGINT NOT NULL, `count` BIGINT NOT NULL, PRIMARY KEY(`id`) ) ENGINE = InnoDB;

代码如下:
package main

import (  
        "bufio"
        "github.com/jinzhu/gorm"
        _ "github.com/jinzhu/gorm/dialects/mysql"
        "log"
        "os"
)

type Record struct {  
        Id    int64 `gorm:"column:id;type:bigint;primary_key",db:"id"`
        Count int64 `gorm:"column:count;type:bigint",db:"count"`
}

func (Record) TableName() string {  
        return "Record"
}

func main() {  
        var bio *bufio.Reader
        var db *gorm.DB
        var err error
        log.Printf("############# main")
        bio = bufio.NewReader(os.Stdin)
        bio.ReadString('\n')

        // 连接数据库
        db, err = gorm.Open("mysql", "root:123456@/test?charset=utf8&parseTime=True&loc=Local")
        if err != nil {
                panic("db link failed")
        }
        defer db.Close()

        log.Printf("db link")
        bio = bufio.NewReader(os.Stdin)
        bio.ReadString('\n')

        // 查找数据
        var record Record
        queryResult := db.Where("id = ?", 20).Find(&record)
        if queryResult.RecordNotFound() {
                log.Printf("Not found")
                return
        }

        log.Printf("############ pass NOTFOUND")
        bio = bufio.NewReader(os.Stdin)
        bio.ReadString('\n')

        //if queryResult.Error != nil {
        //      log.Fatalf("Error %s ", queryResult.Error)
        //      return
        //}
        log.Printf("search ok ")

        // 更新数据
        if err = db.Model(&record).UpdateColumn("count", record.Count+1).Error; err != nil {
                panic(err)
        }
        log.Printf("update ok ")
}

复现步骤,就是
1.运行程序,连接db
2.关闭数据库
3.回车继续程序,程序执行到查询id = 20,这时候db连接是有错误的,但是找到数据、非RecordNotFound的错误都是返回true
4.开启数据库
5.回车继续程序,程序执行到更新数据库的数据,传入record主键id是一个空,导致会更新全表,并且覆盖所有count = 1

update.png

幸好表很大,导致超时,该类操作的数据都被回滚了