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 · 2897 次点击
    这是一个创建于 405 天前的主题,其中的信息可能已经有所发展或是发生改变。

    事情的起因是这样的。 一个方法要查询 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 这样的自信,哦,原来是因为懒和没经验,没被毒打过
    xausky
        27
    xausky  
       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   ·   实用小工具   ·   1521 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 17:11 · PVG 01:11 · LAX 09:11 · JFK 12:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.