<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golang on 认真的雪</title><link>https://blog.wjhe.top/categories/golang/</link><description>Recent content in Golang on 认真的雪</description><generator>Hugo -- gohugo.io</generator><language>zh</language><lastBuildDate>Thu, 28 May 2026 10:07:03 +0800</lastBuildDate><atom:link href="https://blog.wjhe.top/categories/golang/index.xml" rel="self" type="application/rss+xml"/><item><title>GORM 事务中的隐藏陷阱：return nil 不代表没事发生</title><link>https://blog.wjhe.top/gorm-%E4%BA%8B%E5%8A%A1%E4%B8%AD%E7%9A%84%E9%9A%90%E8%97%8F%E9%99%B7%E9%98%B1return-nil-%E4%B8%8D%E4%BB%A3%E8%A1%A8%E6%B2%A1%E4%BA%8B%E5%8F%91%E7%94%9F/</link><pubDate>Thu, 28 May 2026 10:07:03 +0800</pubDate><guid>https://blog.wjhe.top/gorm-%E4%BA%8B%E5%8A%A1%E4%B8%AD%E7%9A%84%E9%9A%90%E8%97%8F%E9%99%B7%E9%98%B1return-nil-%E4%B8%8D%E4%BB%A3%E8%A1%A8%E6%B2%A1%E4%BA%8B%E5%8F%91%E7%94%9F/</guid><description>&lt;h2 id="背景"&gt;背景
&lt;/h2&gt;&lt;p&gt;在 Go + GORM 的日常开发中，事务写法通常如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 业务逻辑&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 回滚&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 更多操作...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#75715e"&gt;// 提交&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个模型很直觉：返回 &lt;code&gt;nil&lt;/code&gt; → 提交事务，返回 &lt;code&gt;error&lt;/code&gt; → 回滚事务。&lt;/p&gt;
&lt;p&gt;但最近踩了一个坑：&lt;strong&gt;在事务内吞掉了错误并返回 &lt;code&gt;nil&lt;/code&gt;，GORM 却报了 &lt;code&gt;commit unexpectedly resulted in rollback&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="问题复现"&gt;问题复现
&lt;/h2&gt;&lt;p&gt;简化后的伪代码如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 第一条 SQL：创建记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordA&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Is&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrDuplicatedKey&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 唯一键冲突，认为是可以接受的，跳过&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; &lt;span style="color:#75715e"&gt;// ← 问题出在这里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 第二条 SQL：创建另一条记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordB&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;预期行为：遇到重复键冲突时，跳过并提交事务。&lt;/p&gt;
&lt;p&gt;实际行为：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: commit unexpectedly resulted in rollback
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;事务直接失败，&lt;code&gt;err&lt;/code&gt; 不为 &lt;code&gt;nil&lt;/code&gt;。&lt;/p&gt;
&lt;h2 id="根因分析"&gt;根因分析
&lt;/h2&gt;&lt;p&gt;关键在于 GORM（以及底层的数据库驱动）的事务状态机：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SQL 执行报错时，数据库驱动会将事务标记为 &lt;code&gt;aborted&lt;/code&gt; 状态。&lt;/strong&gt; PostgreSQL 等数据库在遇到约束违反（如唯一键冲突）后，当前事务即进入 aborted 状态，后续所有操作都会被拒绝，直到执行 ROLLBACK。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;GORM 的 &lt;code&gt;Transaction()&lt;/code&gt; 方法看到你返回 &lt;code&gt;nil&lt;/code&gt;，就会尝试 &lt;code&gt;COMMIT&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;但 COMMIT 一个已经 aborted 的事务，数据库会返回错误&lt;/strong&gt;——因为事务已经被隐式回滚了，无法提交。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;时序如下：&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;tx.Create(&amp;amp;recordA) → 唯一键冲突，PostgreSQL 标记事务为 aborted
return nil → GORM 尝试 COMMIT
COMMIT → 数据库拒绝：事务已经是 aborted 状态
 → GORM 收到错误，报 &amp;#34;commit unexpectedly resulted in rollback&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;核心问题：你吞掉了错误，但事务状态并没有&amp;quot;恢复原状&amp;quot;。数据库驱动已经记住了这个错误。&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="解决方案"&gt;解决方案
&lt;/h2&gt;&lt;h3 id="思路把可预期的冲突包装成一个特殊的错误类型在事务外处理"&gt;思路：把&amp;quot;可预期的冲突&amp;quot;包装成一个特殊的错误类型，在事务外处理
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;第一步：定义业务错误类型&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ErrDuplicated 重复键冲突的业务错误&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ErrDuplicated&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;record already exists&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第二步：事务内返回该错误，而不是 return nil&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transaction&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DB&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordA&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Is&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;gorm&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ErrDuplicatedKey&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ErrDuplicated&lt;/span&gt; &lt;span style="color:#75715e"&gt;// ← 返回业务错误，让事务正常回滚&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;tx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Create&lt;/span&gt;(&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;recordB&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;第三步：在事务外部区分处理&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Is&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ErrDuplicated&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 重复键冲突是可预期的，正常处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Println&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;记录已存在，跳过&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 其他错误正常上报&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="为什么这样是对的"&gt;为什么这样是对的？
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;事务内部：任何错误都如实返回，让 GORM 执行 ROLLBACK，保持事务状态干净。&lt;/li&gt;
&lt;li&gt;事务外部：通过错误类型区分&amp;quot;业务可接受的冲突&amp;quot;和&amp;quot;真正的失败&amp;quot;。&lt;/li&gt;
&lt;li&gt;不会触碰数据库事务状态机的&amp;quot;禁区&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="总结"&gt;总结
&lt;/h2&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;做法&lt;/th&gt;
					&lt;th&gt;结果&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;事务内吞掉 DB 错误，返回 &lt;code&gt;nil&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;事务状态已 aborted，COMMIT 必然失败&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事务内返回自定义错误，外部判断处理&lt;/td&gt;
					&lt;td&gt;事务正常 ROLLBACK，业务逻辑正确分流&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;记住：GORM 事务中，&lt;code&gt;return nil&lt;/code&gt; 永远意味着&amp;quot;请提交&amp;quot;。如果你不能提交，就不要返回 &lt;code&gt;nil&lt;/code&gt;。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即使某个错误在业务上是&amp;quot;可以接受的&amp;quot;，也要在事务外部去接受它，而不是在事务内部悄悄吞掉它。&lt;/p&gt;</description></item><item><title>golang go常用命令</title><link>https://blog.wjhe.top/golang-go%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</link><pubDate>Thu, 24 Apr 2025 16:29:00 +0800</pubDate><guid>https://blog.wjhe.top/golang-go%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</guid><description>&lt;p&gt;查看某个包的所有版本&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;go list -m -versions &amp;lt;pkg&amp;gt;&lt;/code&gt;
&lt;code&gt;go list -m -versions github.com/go-kratos/kratos/v2&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;查看某个二进制的构建信息&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;code&gt;go version -m &amp;lt;path&amp;gt;&lt;/code&gt;
&lt;code&gt;go version -m /usr/bin/go/bin/go&lt;/code&gt;&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>使用 File Watcher 和 goimports-reviser 自动格式化 Golang 代码</title><link>https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/</link><pubDate>Sat, 28 Oct 2023 15:51:00 +0800</pubDate><guid>https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/</guid><description>&lt;p&gt;goimports-reviser 是一个管理 import 分组的工具，当前也已支持（ gofmt ）格式化代码。github 地址 [https://github.com/incu6us/goimports-reviser][https_github.com_incu6us_goimports-reviser]&lt;/p&gt;
&lt;p&gt;这里来&lt;/p&gt;
&lt;h2 id="安装-goimports-reviser"&gt;安装 goimports-reviser
&lt;/h2&gt;&lt;pre&gt;&lt;code&gt;go install -v github.com/incu6us/goimports-reviser/v3@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="设置-file-watcher"&gt;设置 File Watcher
&lt;/h2&gt;&lt;p&gt;Goland 打开 ***文件 →设置 → 工具→File Watcher ***&lt;/p&gt;
&lt;p&gt;新增一个自定义 ，参数如下&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;配置项&lt;/th&gt;
					&lt;th&gt;值&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;文件类型&lt;/td&gt;
					&lt;td&gt;Go文件&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;作用域&lt;/td&gt;
					&lt;td&gt;当前文件&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;程序&lt;/td&gt;
					&lt;td&gt;goimports-reviser&lt;/td&gt;
					&lt;td&gt;goimports-reviser 可执行程序路径&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;实参&lt;/td&gt;
					&lt;td&gt;-rm-unused -format -use-cache -company-prefixes=git.test.com $FilePath$&lt;/td&gt;
					&lt;td&gt;这里没有加 -set-alias ，根据个人需求，prefixes是私有库地址&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;要刷新的输出路径&lt;/td&gt;
					&lt;td&gt;$FileName$&lt;/td&gt;
					&lt;td&gt;这个一定要加，不然会容易出现 “文件缓存冲突”&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;自动保存编辑的文件以触发观察程序&lt;/td&gt;
					&lt;td&gt;false&lt;/td&gt;
					&lt;td&gt;取消勾选，否则容易出现还没编辑完成就被格式化了&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如图所示&lt;/p&gt;
&lt;p&gt;&lt;img alt="2023-11-02_17-55.png" class="gallery-image" data-flex-basis="280px" data-flex-grow="116" height="726" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/2023-11-02_17-55-2.png" srcset="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/2023-11-02_17-55-2_hu_78b7da0c5f0ed269.png 800w, https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/2023-11-02_17-55-2.png 847w" width="847"&gt;&lt;/p&gt;
&lt;h2 id="取消保存时的操作"&gt;取消保存时的操作
&lt;/h2&gt;&lt;p&gt;Goland保存时会默认&lt;em&gt;重新格式化代码&lt;/em&gt;和*优化 import ，*因为 goimports-reviser 会帮我们做 goimports 和 gofmt 操作，我们保存时没必要再重新执行这些功能。这个根据个人情况修改&lt;/p&gt;
&lt;p&gt;Goland 打开 ***文件 →******设置 → 工具→保存时的操作 ，***取消勾选 &lt;strong&gt;重新格式化代码、优化 import&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;如图所示&lt;/p&gt;
&lt;p&gt;&lt;img alt="image2023-7-2_15-16-9.png" class="gallery-image" data-flex-basis="276px" data-flex-grow="115" height="831" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/image2023-7-2_15-16-9-2.png" srcset="https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/image2023-7-2_15-16-9-2_hu_ff84064497f0ade1.png 800w, https://blog.wjhe.top/%E4%BD%BF%E7%94%A8-file-watcher-%E5%92%8C-goimports-reviser-%E8%87%AA%E5%8A%A8%E6%A0%BC%E5%BC%8F%E5%8C%96golang%E4%BB%A3%E7%A0%81/image2023-7-2_15-16-9-2.png 956w" width="956"&gt;&lt;/p&gt;</description></item><item><title>nginx 代理 golang，禁止访问目录的办法</title><link>https://blog.wjhe.top/nginx-%E4%BB%A3%E7%90%86-golang%E7%A6%81%E6%AD%A2%E8%AE%BF%E9%97%AE%E7%9B%AE%E5%BD%95%E7%9A%84%E5%8A%9E%E6%B3%95/</link><pubDate>Thu, 16 Jul 2020 19:03:50 +0800</pubDate><guid>https://blog.wjhe.top/nginx-%E4%BB%A3%E7%90%86-golang%E7%A6%81%E6%AD%A2%E8%AE%BF%E9%97%AE%E7%9B%AE%E5%BD%95%E7%9A%84%E5%8A%9E%E6%B3%95/</guid><description>&lt;p&gt;项目是用golang作为服务器，nginx负责反向代理。&lt;/p&gt;
&lt;p&gt;但是现在有一个问题，golang的web服务器，目录浏览不能自定义关闭。&lt;/p&gt;
&lt;p&gt;最初是想nginx代理静态文件，然后后台请求反向代理到golang，但是这样有更新每次都要git pull两次，有点麻烦。最后还是选择向nginx下手。目录浏览的路径最后肯定是 / ,然后除去根目录， 其它文件夹肯定是/xxx/这样的，直接一个通配符就好了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location ~ (/.*/)${ return 404;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目前够用了。&lt;/p&gt;</description></item></channel></rss>