我在 Java 的 finally 块里写了个 return 做收尾,结果 try 块里本该抛出的异常凭空消失了、本该返回的值也被悄悄换掉了,一个本该报错的方法竟然正常返回:一次 finally 里 return 吞掉了 try 结果的深度复盘

我有个方法 try 里做主要逻辑、finally 里做收尾,图省事在 finally 里也写了 return(或 finally 里调的清理方法会抛异常)。线上出了怪事:某个本该抛异常让上层感知失败的方法竟然正常返回了;有的方法 try 里明明 return 了算好的结果、实际返回的却是另一个值;异常和正确返回值都像凭空消失了,问题被掩盖直到下游拿着错误数据出更大乱子。复盘 try-finally 才搞懂:无论 try 正常结束、return 还是抛异常,finally 都一定执行;而 finally 里有 return,它会覆盖掉 try 的 return 值;finally 里抛异常或 return,会吞掉 try 正在抛的异常——finally 是最后执行的、有最终决定权,会悄悄盖掉 try 的结果或异常。我以为 finally 只是收尾不影响主流程,可里面的 return 篡改了整个方法的结果、把失败伪装成了成功。这篇复盘从故障现场讲到 try-finally 执行规则、finally 为何会吞结果、各组合的结果,再到 finally 绝不 return、清理异常别逃逸、用 try-with-resources(清理异常作为 suppressed 不掩盖业务异常)的完整正解,以及其他收尾善后改变吞掉主流程结果的坑,和辅助收尾环节不该改变或吞掉主流程结论、把失败抹成成功危害最大、让辅助环节各司其职的认知。

我在 Java 的 finally 块里写了个 return 做收尾,结果 try 块里本该抛出的异常凭空消失了、本该返回的值也被悄悄换掉了,一个本该报错的方法竟然"正常"返回:一次 finally 里 return/抛异常吞掉了 try 结果、误以为收尾动作不影响主流程的深度复盘

那个"明明出错了,方法却'正常'返回、异常凭空消失"的诡异 bug,藏在我一个看似无害的 finally 块里。我有个方法,try 里做主要逻辑、finally 里做收尾(关资源、记录什么的),我图省事在 finally 里也写了 return(或者 finally 里调的清理方法可能抛异常)。功能测试好像没问题。可线上出了怪事:某个本该抛出异常、让上层感知到失败的方法,竟然"正常"返回了;还有的方法,try 里明明 return 了算好的结果,实际返回的却是另一个值;异常和正确的返回值,都像凭空消失了,问题被悄悄掩盖,直到下游拿着错误的数据出了更大的乱子才暴露。复盘 Java 的 try-finally 执行机制,我才彻底搞懂,后背发凉:问题出在 finally 块里的 return(或抛出的异常),会覆盖/吞掉 try 块里的 return 值或异常Java 的规则是:无论 try 块正常结束、return、还是抛异常,finally 块都一定会执行;而如果 finally 里有 return,它会"最终拍板"——覆盖掉 try 里的 return 值;如果 finally 里抛出异常(或 return),它会吞掉 try 里正在抛出的异常;也就是说,finally 是"最后执行"的,它的 return/throw 拥有"最终决定权",会悄悄盖掉 try 辛苦算出的结果或要抛的异常;我以为 finally 只是"做点收尾、不影响主流程",可我在里面写的 return,却篡改了整个方法的结果;而 finally 里清理代码不小心抛出的异常,则把 try 里真正重要的业务异常给吞了——上层只看到收尾的小异常、或干脆什么都没看到。根本原因是:finally 块一定执行,且它里面的 return 会覆盖 try 的返回值、它抛出的异常会吞掉 try 正在抛的异常(finally 拥有最终决定权);我在收尾的 finally 里写了 return(或它抛了异常),悄悄篡改/吞掉了 try 的真实结果和异常。问题的根,是 finally 里的 return/抛异常吞掉了 try 的结果——finally 最后执行、有最终决定权,会覆盖 try 的返回值、吞掉 try 的异常;我误以为收尾动作不影响主流程结果。这篇就把这次"finally 吞结果"的坑,从头到尾复盘一遍。

故障现场:finally 的 return,盖掉了 try 的一切

问题在于 finally 的 return/异常覆盖、吞掉了 try 的结果:

// 坑1: finally 里 return, 覆盖了 try 里的 return 值
public int compute() {
    try {
        return 1;          // 算好了, 想返回 1
    } finally {
        return 2;          // ✗ finally 里也return! 最终返回的是 2, try的1被丢弃!
    }
}
// compute() 返回 2, 不是 1 —— try的return被finally覆盖。

// 坑2: finally 里 return(或抛异常), 吞掉了 try 正在抛的异常
public int risky() {
    try {
        throw new BusinessException("库存不足");   // 想抛出业务异常让上层处理
    } finally {
        return -1;          // ✗ finally里return! 异常被吞掉, 方法"正常"返回-1!
    }
}
// risky() 不会抛异常, 而是返回 -1 —— 调用方以为成功了, 真正的"库存不足"消失了!

// 坑3: finally 里清理代码抛了异常, 吞掉了 try 的业务异常
public void process() {
    Resource r = open();
    try {
        doBusiness();       // 这里抛了重要的业务异常 BusinessException
    } finally {
        r.close();          // ✗ 若close()也抛异常(IOException), 它会"压住"上面的BusinessException!
                            //   上层只看到IOException, 真正的BusinessException被吞了。
    }
}

/*
为什么会这样(try-finally 的执行规则):
  - finally 块【一定会执行】: 无论try正常结束/return/抛异常, 都先执行finally再真正离开方法;
  - finally 是"最后执行"的, 它有"最终决定权":
    * finally 里 return → 覆盖 try 的 return 值(try的return被丢弃);
    * finally 里 return 或 抛异常 → 吞掉 try 正在抛出的异常(异常被丢弃, 不再往上传);
  - 所以 finally 里的 return / throw, 会悄悄"盖掉"try辛苦产生的结果或异常。

  危害:
  - 异常被吞 → 失败被伪装成成功, 上层无感, 问题被掩盖, 数据错乱(最危险);
  - 返回值被覆盖 → 返回了错误的结果;
  - 都极难排查(代码看起来都"对", 异常却凭空消失)。

★ 核心: finally一定执行且"最后拍板"; finally里的return会覆盖try的返回值、finally抛异常会吞掉try的异常;
  finally(收尾)只该做"清理"(关资源等), 绝不该用return改变返回值、也别让它抛出的异常掩盖业务异常。

看着那个"抛了异常却'正常'返回"的方法,我又懊恼又后怕:"我以为 finally 就是个'扫尾的',随手写个 return 收个尾、关个资源……谁知道它'最后执行'还'最终拍板',我那个 return 直接把 try 抛的异常给吞了,失败被伪装成了成功!"这个坑最危险的地方在于:把"失败"伪装成了"成功"——异常被吞、方法正常返回,上层完全无感,以为一切正常,继续拿着错误的状态往下走,直到酿成更大的、更难追溯的故障;而且代码编译毫无问题、看起来都"",异常却凭空消失,定位起来极其困难下面就来拆解,finally 到底该怎么用。

第一件事:搞懂 try-finally 的执行规则

我顺着这次事故,把 try-finally 的执行机制彻底理清了。

try-finally 怎么执行? finally 为什么会吞掉结果?

【核心: finally一定执行、且"最后拍板"; finally里的return覆盖try的返回值、finally抛的异常吞掉try的异常;
   finally只该做清理、绝不在里面return或让它抛异常掩盖业务异常; 收尾不该改变/吞掉主流程的结果】

1. finally 一定执行:
   - 无论 try 正常结束、return、还是抛异常, finally 块都会在"真正离开方法之前"执行;
   - 用途: 释放资源、解锁、收尾——保证"不管怎样都要做的清理"。

2. finally 的"最终决定权"(坑的根源):
   - finally 是最后执行的, 如果它本身有 return 或抛异常, 就会"覆盖/吞掉"try的:
     * finally return X → 方法返回X, try的return值被丢弃;
     * finally 抛异常 → 这个新异常向上传, try正在抛的异常被丢弃(吞掉);
     * finally return → 也会吞掉try正在抛的异常(异常没了, 正常返回);
   - 一句话: finally里的return/throw, 拥有压倒try的"最终话语权"。

3. 各种组合的结果:
   - try return A, finally 正常 → 返回A(正常, finally只清理);
   - try return A, finally return B → 返回B(A被覆盖! 坑);
   - try throw E, finally 正常 → 抛E(正常, 异常正常往上传);
   - try throw E, finally return B → 返回B, E被吞(坑! 失败变成功);
   - try throw E, finally throw E2 → 抛E2, E被吞(坑! 业务异常被清理异常掩盖);
   - try 改了要返回的变量, finally 再改它 → 对基本类型/不可变, finally的修改不影响已定的返回值
     (return时值已确定); 但对可变对象的内容修改会生效——细节要清楚。

4. 正解原则:
   ① finally 里【绝不要 return】——别用finally决定返回值, 它会覆盖try;
   ② finally 里的清理代码【不要让它抛出的异常逃逸】——用try-catch包住清理、或用资源会自动处理;
   ③ 用 try-with-resources(Java7+)自动关资源——它正确处理了"清理异常不掩盖业务异常"
      (清理时的异常会作为"被抑制的异常suppressed"附在主异常上, 而非覆盖它);
   ④ finally 只做"清理/收尾", 不改变方法的"结果(返回值/异常)"。

5. 本质: 收尾/善后的动作, 不应改变或吞掉主流程的结论(尤其是错误信号)
   - finally(收尾)的职责是"清理", 不是"决定结果";
   - 让收尾动作去 return/抛异常, 等于让"善后"篡改了"主流程"的结论, 还可能掩盖真正的错误。

一句话: finally一定执行且最后拍板, 里面的return会覆盖try返回值、抛的异常会吞掉try异常; finally只做清理、
   绝不return、别让清理异常逃逸(用try-with-resources); 收尾动作不该改变或吞掉主流程的结果与错误。

这套认知,是整个坑的根。finally 一定执行:无论 try 正常/return/抛异常,finally 都在离开方法前执行,用途是保证清理finally 的最终决定权:它最后执行,里面的 return 覆盖 try 的返回值、抛的异常吞掉 try 的异常各种组合:try return A + finally return B→返回 B(A 没了);try throw E + finally return B→返回 B(E 被吞,失败变成功);try throw E + finally throw E2→抛 E2(E 被掩盖)正解原则:finally 绝不 return、清理异常别逃逸、用 try-with-resources、finally 只清理不改结果本质:收尾/善后的动作不应改变或吞掉主流程的结论(尤其错误信号)一句话:finally 一定执行且最后拍板,里面的 return 会覆盖 try 返回值、抛的异常会吞掉 try 异常;finally 只做清理、绝不 return、别让清理异常逃逸(用 try-with-resources);收尾动作不该改变或吞掉主流程的结果与错误。

第二件事:正解——finally 只清理,用 try-with-resources

知道了 finally 会吞结果,正解就清楚了:finally 只做清理、绝不 return,资源用 try-with-resources。

// 正解1: finally 里绝不 return, 只做清理(本次该守的)
public int compute() {
    try {
        return doWork();       // try 决定返回值
    } finally {
        cleanup();             // ✓ finally 只清理, 不 return, 不覆盖返回值
    }
}

// 正解2: finally 里的清理代码, 别让它抛出的异常逃逸(掩盖业务异常)
public void process() {
    Resource r = open();
    try {
        doBusiness();          // 可能抛业务异常
    } finally {
        try {
            r.close();         // ✓ 清理放进自己的try-catch
        } catch (Exception e) {
            log.warn("关闭资源失败", e);   // 记下来, 但别让它逃逸覆盖业务异常
        }
    }
}

// 正解3: 用 try-with-resources(Java7+)——最推荐, 自动且正确处理清理异常
public void processV2() {
    try (Resource r = open()) {     // 自动关闭, 无需finally
        doBusiness();               // 若这里抛业务异常E, close()的异常会作为"suppressed"附在E上,
    }                               // ✓ 业务异常E正常往上传, 清理异常不会覆盖它!
    // 可用 e.getSuppressed() 拿到被抑制的清理异常, 既不丢业务异常、也不丢清理异常。
}

// 正解4: 想在finally做收尾又要保留异常/返回值 —— 别return, 用变量/重抛
public int safe() {
    int result;
    try {
        result = doWork();
        return result;
    } finally {
        cleanup();              // 只清理; 绝不 return result(没必要且危险), 也别在这throw新异常
    }
}

// 反例(别这样):
// try { ... } finally { return x; }           // ✗ 覆盖try的返回值/吞异常
// try { risky(); } finally { r.close(); }      // △ 若close()抛异常会掩盖risky的异常, 用try-with-resources

// 核心: finally只做清理、绝不return; 清理代码的异常别让它逃逸(自己catch或用try-with-resources);
//   资源管理优先用try-with-resources(自动关闭 + 清理异常作为suppressed不掩盖业务异常)。

这套正解的关键,是让 finally(收尾)只"清理",绝不去"决定结果"finally 绝不 return:别用 finally 决定返回值,它会覆盖 try——这正是本次的祸根。清理异常别逃逸:清理代码放进自己的 try-catch、记日志但别让它覆盖业务异常。用 try-with-resources(最推荐):自动关闭资源,且清理时的异常会作为"被抑制的异常(suppressed)"附在业务异常上,而非覆盖它——业务异常正常往上传、清理异常也不丢。核心是:finally 只清理,资源管理优先用 try-with-resources。

第三件事:其他几个"收尾/善后动作改变了主流程结果"的坑

顺着这次 finally,我把"收尾/兜底动作擅自改变或吞掉主流程结论"的几类坑也一并理了:

几类"收尾/善后改变/吞掉主流程结果"的坑:

坑1: catch 里吞异常不重抛/不记录(同578)——catch(Exception e){} 空捕获, 错误被吃掉;
   正解: catch里至少记日志/处理/重抛, 别让错误无声消失。

坑2: 拦截器/中间件/AOP 改写了返回值或吞了异常——切面里catch了异常返回默认值, 掩盖了真实错误;
   正解: 切面要么透传异常、要么明确转换并记录, 别悄悄吞。

坑3: 析构/清理函数里抛异常(C++/各语言)——析构抛异常掩盖原异常, 甚至导致terminate;
   正解: 清理/析构里别抛异常, 内部消化。

坑4: 全局异常处理器把所有异常转成200/默认响应——失败被伪装成成功;
   正解: 按错误类型返回正确状态码, 别一律吞成成功。

坑5: 重试/降级逻辑吞掉了根本错误——失败后降级返回兜底值, 但没记录/告警真实失败;
   正解: 降级的同时要记录、告警真实错误, 别让降级掩盖了"系统其实出问题了"。

坑6: defer/ensure/finally 等收尾里改了状态——收尾时顺手改了本该报错的状态, 掩盖问题。

共同的根: "收尾、善后、兜底、清理、拦截"这些【辅助性、本该服务于主流程】的环节,
   一旦擅自【改变了主流程的结果、或吞掉了主流程的错误信号】, 就会把"失败"伪装成"成功"、
   篡改真实结论、掩盖根因; 辅助环节应"各司其职"(清理就只清理、拦截就透传), 不该越权篡改主流程的结论。

这些坑看似不同,根却是同一个:"收尾、善后、兜底、清理、拦截"这些辅助性、本该服务于主流程的环节,一旦擅自改变了主流程的结果、或吞掉了主流程的错误信号,就会把"失败"伪装成"成功"、篡改真实结论、掩盖根因认清这个根("辅助环节各司其职、不越权篡改主流程的结论和错误"),才不会让"善后"反而酿成更大的祸。

第四件事:try-finally 各组合结果 / 正确写法——两张对照表

我把 try-finally 各种组合的结果、以及正确写法,整理成对照表,贴在了团队的 Java 规范里:

try 里 finally 里 最终结果
return A (只清理) 返回 A ✓
return A return B 返回 B(A 被覆盖!)✗
throw E (只清理) 抛 E ✓
throw E return B 返回 B,E 被吞 ✗✗
throw E throw E2 抛 E2,E 被掩盖 ✗
(try-with-resources) close() 抛异常 抛业务异常,close 异常 suppressed ✓
需求 正确做法
关资源 try-with-resources(首选)
必须做清理 finally,但只清理、不 return
清理可能抛异常 清理包进 try-catch,记日志别逃逸
决定返回值 在 try 里 return,别在 finally
保留业务异常 别在 finally throw/return 盖住它

这两张表的核心,第一张是只要 finally 里有 return 或 throw,就会覆盖/吞掉 try 的返回值或异常——其中"try throw + finally return"把失败变成功,最危险;而 try-with-resources 正确处理(清理异常 suppressed、不掩盖业务异常);第二张是finally 只清理、不 return、不放任异常逃逸,资源用 try-with-resources。记住一条:finally 里绝不写 return,也绝不让它抛出的异常盖住 try 的异常。

第五件事:关于 finally 的几组容易想当然的认知

这次事故也让我厘清了几组关于 finally 的、容易想当然的概念:

直觉以为 实际上
finally 只是收尾,不影响结果 它里面的 return/throw 会覆盖/吞掉 try 的结果
finally 里 return 没问题 会覆盖 try 的返回值、吞掉 try 的异常
try 抛了异常就一定会往上传 finally 里 return/throw 会把它吞掉
finally 里关资源很安全 close() 抛异常会掩盖业务异常,用 t-w-r
异常不会凭空消失 被 finally 吞掉就消失了,失败变成功
收尾代码随便写写就行 收尾越权改结果会掩盖真相、酿大祸
编译没问题就逻辑没问题 finally 吞异常编译正常,逻辑严重错

这张表里,我栽的是第一行和第三行:把 finally 当成"不影响结果的收尾",在里面随手写了 return,结果它把 try 该抛的异常给吞了、失败被伪装成了成功厘清这些,核心是一个意识:finally 是"最后执行、最终拍板"的——它里面的 return/throw 拥有压倒 try 的话语权,会覆盖返回值、吞掉异常;所以收尾的 finally 只该"清理",绝不能"决定结果",更不能让收尾时的小问题掩盖了主流程真正的错误。

第六件事:写 finally / 收尾代码时,我现在的自检习惯

现在每当我要写 finally 或任何收尾代码,我都会先按这张图问自己:

这张图的精髓,是"finally别return、资源用try-with-resources、清理异常别逃逸"先问想做什么:想 return 就停(留给 try)、关资源就用 try-with-resources、清理可能抛异常就包起来别逃逸这套习惯,让我从"finally 随手 return 收尾"变成了"finally 只清理、绝不改结果"——核心始终是:finally 一定执行且最后拍板,里面的 return 覆盖 try 返回值、抛的异常吞掉 try 异常;finally 只做清理、绝不 return、别让清理异常逃逸(用 try-with-resources);收尾不该改变或吞掉主流程的结果与错误。

我立下的几条规矩

这场"finally 吞掉异常、失败变成功"的事故,换来了我写 Java 时,刻进骨子里的几条铁律:

  1. finally 块一定执行,且是"最后执行、最终拍板"的。
  2. finally 里的 return 会覆盖 try 的返回值;finally 里的 return/throw 会吞掉 try 正在抛的异常。
  3. finally 里绝不要写 return——别用它决定方法的返回值。
  4. finally 里的清理代码别让它抛出的异常逃逸(包进 try-catch、记日志),否则会掩盖业务异常。
  5. 资源管理优先用 try-with-resources:自动关闭,且清理异常作为 suppressed 不掩盖业务异常。
  6. finally(收尾)只做清理,不决定结果;返回值的决定权留在 try 里。
  7. 一切收尾/兜底/拦截环节,都不该擅自改变主流程的结果、或吞掉主流程的错误信号。

附:try-with-resources 如何正确处理"双异常"

借这次的坑,我特意验证了 try-with-resources 是怎么解决"清理异常掩盖业务异常"这个 finally 老大难的——它用"被抑制的异常(suppressed)"机制,两个异常都不丢。

// 一个会在使用和关闭时都抛异常的资源
class Resource implements AutoCloseable {
    void use()   { throw new RuntimeException("业务异常: 处理失败"); }
    public void close() { throw new RuntimeException("清理异常: 关闭失败"); }
}

// 用 try-with-resources: 业务异常是主异常, 关闭异常被"抑制"附在它上面
public void demo() {
    try (Resource r = new Resource()) {
        r.use();          // 抛"业务异常"
    }                     // 自动调close()又抛"清理异常"
    // 结果: 向上抛的是【业务异常】(主), "清理异常"作为 suppressed 附在它上面, 两个都没丢!
}

// 对比: 手写 finally(老写法的坑)
public void demoBad() {
    Resource r = new Resource();
    try {
        r.use();          // 抛"业务异常"
    } finally {
        r.close();        // ✗ 又抛"清理异常" → 它【覆盖】了业务异常! 上层只看到"清理异常", 业务异常丢了
    }
}

// 捕获时能拿到两个异常:
try {
    demo();
} catch (Exception e) {
    System.out.println("主异常: " + e.getMessage());          // 业务异常: 处理失败
    for (Throwable s : e.getSuppressed()) {
        System.out.println("被抑制: " + s.getMessage());       // 清理异常: 关闭失败
    }
}

// 原则: try-with-resources 用"主异常 + suppressed被抑制异常"的机制, 让"业务异常"和"清理异常"
//   都能保留、各归其位; 而手写finally里调close很容易让清理异常【覆盖】业务异常 —— 所以优先用前者。

这段对比的价值,在于它清清楚楚地展示了 try-with-resources 比手写 finally 高明在哪:它分清了"主异常(业务)"和"被抑制的异常(清理)",让收尾时的异常"附属"于主异常而非"覆盖"它这正是对"收尾不该吞掉主流程错误"这个原则的语言级支持——把"正确处理双异常"这件容易写错的事,交给语言机制保证,而不是依赖每个人都记得在 finally 里小心翼翼地包住清理异常。

写在最后

回头看,这场由"finally 里的 return 吞掉了 try 的异常"引发的、失败被伪装成成功的事故,真正教给我的,远不止"finally 别 return"这一个技巧。它让我对"一个本该'服务于主流程的、辅助性的收尾环节', 一旦被赋予了'改变主流程结论'的能力(还恰好是'最后执行、最终拍板'的位置), 就可能反客为主、悄悄篡改甚至吞掉主流程辛苦得出的真实结果——尤其是把'失败'抹成了'成功'",有了一次刻骨的体会。我栽跟头,是因为我低估了"收尾环节"的能量——我以为 finally 就是个'打扫卫生的', 干不了什么大事;可我忘了它有两个危险的属性: 一是'最后执行'(它说的是最后一句话), 二是我给了它'return/throw'(决定结果)的权力; 一个'最后说话'又'有权改结论'的环节, 自然就盖过了前面所有的努力——try 辛辛苦苦算出的结果、要报告的失败, 全被这个'扫尾的'一句话给改了、吞了。这让我领悟到一个关于"主与辅、收尾与篡改"的深刻认知:在任何流程里, 都有"主流程(产生核心结果)"和"辅助环节(收尾、清理、兜底、拦截)"之分; 辅助环节的本分是'服务、配合'主流程, 而不是'覆盖、篡改、吞掉'主流程的结论;一旦辅助环节越权改变了主流程的结果(尤其是吞掉了'失败/错误'这个最重要的信号), 危害极大: 它会把真相掩盖在一片'看似正常'之下——失败被当成成功、错误凭空消失, 而所有人都被蒙在鼓里, 直到错误的结果在下游酿成更大的、更难追溯的祸;这对一切系统都成立: 收尾的代码、兜底的逻辑、善后的人、传话的中间环节——它们可以清理、可以补充, 但绝不该悄悄改写主角的结论, 尤其不该把'出问题了'这个警报给关掉这给了我一种设计"辅助/收尾环节"时的清醒:每当我写一个"收尾、兜底、拦截、善后"的环节时,要警惕它"越权"——问自己"这个辅助环节, 会不会改变了主流程本该产生的结果?会不会吞掉了本该往上报的错误?"; 让它严守'清理/辅助'的本分, 把"决定结果、报告成败"的权力留给主流程;"让辅助环节各司其职、绝不越权篡改或吞掉主流程的结果与错误信号",是避免'善后反而掩盖真相、失败被伪装成成功'的关键认清辅助收尾环节不该改变或吞掉主流程结论、把失败抹成成功危害最大、让辅助环节各司其职——这,是我用一次 finally 吞异常的事故,换来的、关于 Java、也关于如何摆正主流程与收尾环节关系的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次手快想在 finally 里写个 return 之前顿一下、把它挪回 try、或干脆改用 try-with-resources,那我对着那个"抛了异常却正常返回"的诡异方法排查的这段时间,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我在 Go 里用 == 判断一个错误是不是记录不存在,一直好好的,直到某层代码用 %w 把这个错误包装了一下再往上抛,我的 == 判断就突然失效了,把记录不存在当成了未知错误的深度复盘

2026-6-3 1:53:49

技术教程

我为了防止插入重复数据,代码里先查一下存不存在、不存在才插入,单机测试一切正常,可一上并发就冒出了重复记录,因为先检查再插入这两步之间有个窗口、并发请求全挤了进来的深度复盘

2026-6-3 2:04:39

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索