关于 Golang 不处理数据库操作的 error 的一点思考 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
maotao456
V2EX    Go 编程语言

关于 Golang 不处理数据库操作的 error 的一点思考

  •  
  •   maotao456 2023-10-20 14:38:42 +08:00 3409 次点击
    这是一个创建于 753 天前的主题,其中的信息可能已经有所发展或是发生改变。

    事情的起因是这样的。 一个方法要查询 N 次数据库,每一次查询都要 if err , 我实在受不了啦,思索一番后觉得 似乎并不需要在运行时处理数据库操作的 error 。结论如下:

    因为运行时的 error 大概率是连接,基建问题。 如果是 sql 语句问题,早就该在测试环节就发现。 既然是连接问题,那么一个查询报错,其它所有查询都会报错, 所以在每一个查询的地方都做 err check 是没有意义的。

    可是不处理 err 怎么知道数据库有问题呢,怎么保证业务的完整呢?我大概理出了三板斧:

    1. query 的操作不处理 err ,sql 问题留到测试期解决
    2. update, insert 类操作要处理 err, 保证数据安全
    3. 对这些操作做一层包装和封装,每次调用记录 error ,监控该报警报警

    当然,我不确定我这个想法是否考虑周到,可能以偏概全了。 欢迎大家一起讨论一下。

    38 条回复    2023-10-30 15:58:36 +08:00
    qq976739120
        1
    qq976739120  
       2023-10-20 14:40:43 +08:00
    不要忽略任何错误.哪怕打个日志也好. 用上模板写个 if err 也就一秒. 万一出问题帮你节省的时间起码几小时.
    28Sv0ngQfIE7Yloe
        2
    28Sv0ngQfIE7Yloe  
       2023-10-20 14:42:05 +08:00
    之前写 gorm 的时候,无查询结果时也会有 err 不知道现在是什么样了
    codehz
        3
    codehz  
       2023-10-20 15:03:16 +08:00   1
    数据库查询错误,可能是数据库迁移没做好,这个测试环境可能无法复现(因为生产环境和测试环境很有可能用的不是同一个数据库,注意这里的错误不一定指的是某个表不存在,而是说可能出现一些异常数据导致查询错误)
    lsk569937453
        4
    lsk569937453  
       2023-10-20 15:13:18 +08:00
    必须要每个 error 都处理。

    我就说一个最简单的场景,一个请求进来,判断是不是合法用户(根据 token 查询数据库中是否存在),如果不存在,则在爬虫表里记录该请求的信息。
    伪代码大概如下:

    var count int64
    err1 := dao.TestDb.Model(&dao.UserDao{}).Count(&count).Error
    //用户信息表里没有,则记录爬虫
    if count==0{
    err2:= dao.TestDb.Create(userDao)
    }

    如果一个合法用户的请求进来,因为网络抖动出错了,也会导致 count=0 并且 err1 不为空,你没有处理 err1,所以后面 insert 成功了(你无法猜测网络波动的时间,只能尽可能的去处理所有的 error)。
    hellodudu86
        5
    hellodudu86  
       2023-10-20 15:24:45 +08:00
    线上出现问题的时候,你就会后悔没有每个 err 都处理并打印日志了
    inhzus
        6
    inhzus  
       2023-10-20 15:25:36 +08:00
    写代码一时爽,查线上问题火葬场
    mcfog
        7
    mcfog  
       2023-10-20 15:36:43 +08:00 via Android   1
    goland 生产力搞起,输入
    err.rr
    按 tab
    见证奇迹
    guanzhangzhang
        8
    guanzhangzhang  
       2023-10-20 16:41:50 +08:00
    每个 err 都要处理,更何况设计到网络这种的
    liyunlong41
        9
    liyunlong41  
       2023-10-20 16:44:42 +08:00 via iPhone
    很多因素会导致数据库报错,cpu 、内存、磁盘、其它慢 sql 影响、db 的定时任务、机器老化等。甚至使用的 orm 库可能有 bug ,导致某些正常连接有问题。建议还是每个错误都捕获,写 err 麻烦的话,goland 敲 err 然后直接 tab 键就自动补全了。
    chaleaochexist
        10
    chaleaochexist  
       2023-10-20 16:44:45 +08:00
    在 copilot 的帮助下, 没有太大问题.
    考虑到费用问题, 也有很多免费的替代, 今天北大刚出了一款类似产品.
    dobelee
        11
    dobelee  
       2023-10-20 16:46:23 +08:00
    查线上问题的时候你把不得每行一条 log 。
    monster1priest
        12
    monster1priest  
       2023-10-20 17:05:38 +08:00
    能处理就处理,处理不了就抛出去,但是永远不要吞异常,
    k9982874
        13
    k9982874  
       2023-10-20 17:08:56 +08:00
    一楼说的没错,你起码要打个 log ,不然线上出问题,找到你这你说不清楚这锅就得你背
    Sendya
        14
    Sendya  
       2023-10-20 17:19:17 +08:00
    @Morii First Last 查询 API 为单条查询设计,查询不到时会 ErrRecordNotFound 错误,使用 Take API 查询单条时,查询不到不会报错。应该是使用 API 的问题
    28Sv0ngQfIE7Yloe
        15
    28Sv0ngQfIE7Yloe  
       2023-10-20 17:24:10 +08:00
    @Sendya 那可能是我对 Gorm 不熟悉的问题
    zhangyq008
        16
    zhangyq008  
       2023-10-20 17:36:39 +08:00
    没必要,err 该处理处理
    ychost
        17
    ychost  
       2023-10-20 17:42:52 +08:00
    还是打印日志为妙
    CEBBCAT
        18
    CEBBCAT  
       2023-10-20 17:54:09 +08:00
    有一些 fail fast 的文档,这会儿时间不多,可以 Google 「 program design check error rare panic OR fail 」
    dw2693734d
        19
    dw2693734d  
       2023-10-20 17:59:48 +08:00 via iPhone
    其实 postgres 已经把错误日志收集了
    dqzcwxb
        20
    dqzcwxb  
       2023-10-20 18:06:05 +08:00
    谢谢你对测试的信任
    Kumo31
        21
    Kumo31  
       2023-10-20 18:14:13 +08:00
    可以看下 Errors are values 这篇文章: https://go.dev/blog/errors-are-values
    pkoukk
        22
    pkoukk  
       2023-10-20 18:35:33 +08:00
    db 的 error 你都敢不处理,胆子好大啊,链式操作怎么办?前一步操作失败该回滚了你还继续么
    一个 crud 的接口,线上数据库字段变更没成功,查询操作失败了,抛个服务错误出来,你可以在网关加监控,捕捉到
    现在 error 不处理了,等着客户投诉你你才知道啊,这么任性的么
    sadfQED2
        23
    sadfQED2  
       2023-10-20 19:24:31 +08:00 via Android
    @pkoukk 回滚?我目测 op 的数据库操作都没开启事物,回滚什么
    Akkuman
        24
    Akkuman  
       2023-10-20 21:31:27 +08:00
    对于数据库错误,我是直接 panic 出去了,事务内的会自动回滚,然后全局 recover 了特定于数据库的报错,提示给用户的就是数据库报错,日志里面记录了错误的详细信息
    K2K2
        25
    K2K2  
       2023-10-20 21:37:16 +08:00
    首先写结论:必须处理 if err 判断,其实就是一个熟悉的过程,搞熟悉了就不介意了。就像菇凉一样,开始丑点,看顺了就好。
    ShuWei
        26
    ShuWei  
       2023-10-20 23:56:02 +08:00
    是谁给了 op 这样的自信,哦,原来是因为懒和没经验,没被毒打过
    irrigate2554
        27
    irrigate2554  
       2023-10-21 14:55:20 +08:00
    我是 if err != nill; panic(err); 然后在一个统一的地方全局处理,很多 err 确实属于没法处理,直接统一中断并且记录就好了,但是千万不要丢掉它。
    maotao456
        28
    maotao456  
    OP
       2023-10-21 22:27:02 +08:00
    @sadfQED2 你是怎么目测出没用事务的? 我是说 query 类操作不处理 err ,insert, update 要处理 err 。 业务系统都是 query 多过 insert 和 update 。
    maotao456
        29
    maotao456  
    OP
       2023-10-21 22:27:48 +08:00
    @xausky 我有提到, 将数据库操作封装一层,统一记录 error 。
    maotao456
        30
    maotao456  
    OP
       2023-10-21 22:29:42 +08:00
    @pkoukk 你们都不看完内容吗,我说 insert 和 update 的处理 error ,这就覆盖了链式操作。
    maotao456
        31
    maotao456  
    OP
       2023-10-21 22:31:29 +08:00
    @lsk569937453 这个提到了一个我没考虑到的场景,确实存在这个问题。 不是单纯的查询不到的问题。我再犹豫一下。谢谢
    maotao456
        32
    maotao456  
    OP
       2023-10-21 22:38:18 +08:00
    我补充一下:为什么我提出 query 类操作不处理 err ,我的考虑是这样的,
    查询结果无非两种,是 struct 或者是 slice

    一般来说,无论是什么查询结果,我们都会且有必要验证结果的有效性(而不仅仅是 err )。
    比如说:
    1. 如果查询结果是一个 struct ,那么至少会 if xx != nil
    2. 如果查询结果是一个 slice ,一般至少会判断 len(slice) > 0

    在这两个前提下,无论有没有 err 都会去做的处理。 ( for 类操作甚至不需要不需要提前 len(slice))
    重点: 一旦发生 error, 那么这两种查询的结果一定是 nil 和 len(slice) = 0, 所以对于预期来说并没有任何差异。
    maotao456
        33
    maotao456  
    OP
       2023-10-21 22:47:15 +08:00
    @maotao456 并且,if err != nil 的判断并不能替代业务上的 if struct != nil or if len(slice) > 0 , 因为即使没有 error 发生。 我从来不会以 error 有没有来判断数据正不正确。 而是以数据本身判断数据是否正确。

    伪代码如下:

    var entity user
    err := sqlx.Get("select * from xx limit 1;", &user)
    if err != nil {
    logger.Error("xxxxxxxxxxxxxxxxxxxxx");
    return err
    }

    if user.ID == 0 {
    return errors.New("user not found")
    }

    在这段代码中,重点只在与有没有写日志。 有没有 if err 都不影响业务逻辑的流程。

    那么, 换个角度来说,是否所有数据库操作都可以统一写日志呢,业务中的 query 操作是否可以不显示处理 error 呢, 单看这个例子似乎是可以的。

    slice 查询操作场景你们也可以代入看看。
    lanlanye
        34
    lanlanye  
       2023-10-21 23:38:19 +08:00
    这就不得不提 try...catch...的含金量了:)
    javaisthebest
        35
    javaisthebest  
       2023-10-22 12:17:13 +08:00
    要笑死 搞到最后还是 java 的那一套 AOP 爽
    pkoukk
        36
    pkoukk  
       2023-10-23 10:16:45 +08:00
    @maotao456 #30 链式操作就不能 insert->query->update 么?
    另外第二个问题你怎么办,查询出错不报错,前端显示空数据?
    你要是用户,你上来看到报个错血压高,还是看上去自己的数据全没了血压高?
    maotao456
        37
    maotao456  
    OP
       2023-10-23 11:58:12 +08:00
    @pkoukk
    你要是用户,你上来看到报个错血压高,还是看上去自己的数据全没了血压高?

    ----------------------------------------------------
    这个你说得有些道理,我想想
    dyllen
        38
    dyllen  
       2023-10-30 15:58:36 +08:00
    @Morii 还是一样的,go 的 sql 标准库也是这样。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5666 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 01:49 PVG 09:49 LAX 17:49 JFK 20:49
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86