<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Database/Sql on Zampo Blog</title><link>https://blog.cpdd.fyi/tags/database/sql/</link><description>Recent content in Database/Sql on Zampo Blog</description><generator>Hugo</generator><language>zh-cn</language><lastBuildDate>Sat, 04 Jul 2026 14:53:48 +0800</lastBuildDate><atom:link href="https://blog.cpdd.fyi/tags/database/sql/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 连接池报错，别先把 max_open 调大</title><link>https://blog.cpdd.fyi/posts/go-db-connection-pool-max-open/</link><pubDate>Sat, 04 Jul 2026 14:53:48 +0800</pubDate><guid>https://blog.cpdd.fyi/posts/go-db-connection-pool-max-open/</guid><description>&lt;p&gt;线上报 &lt;code&gt;connection pool exhausted&lt;/code&gt;，我见过不少人的第一反应是把 &lt;code&gt;max_open&lt;/code&gt; 调大。&lt;/p&gt;
&lt;p&gt;从 20 调到 100，报警没了，接口也稳了。看起来问题解决了。&lt;/p&gt;
&lt;p&gt;直到另一个服务把它调到 200，还是超时；DBA 又在群里说数据库连接数被打满了。你才发现，自己根本不知道刚才那个数字是在救服务，还是在把数据库往坑里推。&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.cpdd.fyi/images/db-connection-pool/cover.svg" alt="Go 数据库连接池封面"&gt;&lt;/p&gt;
&lt;p&gt;Go 的数据库连接池最容易被误解的地方，不是参数多，而是每个参数保护的对象不一样。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;max_open&lt;/code&gt; 不是油门，是刹车。它限制的是你的 Go 进程同时向数据库伸出去的手。&lt;/p&gt;
&lt;p&gt;连接池报错时，真正要问的不是“要不要加大池子”，而是：现在到底卡在等连接、忘了还连接、频繁建连接、数据库扛不住，还是连接寿命和中间层超时打架？&lt;/p&gt;
&lt;h2 id="连接池不是给你兜底的它是挡在数据库前面的阀门"&gt;连接池不是给你兜底的，它是挡在数据库前面的阀门&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;database/sql&lt;/code&gt; 里的 &lt;code&gt;*sql.DB&lt;/code&gt; 很容易让人误会。&lt;/p&gt;
&lt;p&gt;名字叫 DB，但它不是一条数据库连接。它是一个可以被多个 goroutine 并发使用的句柄，内部会按需要管理一组到底层数据库的连接。&lt;/p&gt;
&lt;p&gt;你执行 &lt;code&gt;Query&lt;/code&gt;、&lt;code&gt;Exec&lt;/code&gt;，它会去拿一条可用连接。没有空闲连接时，如果还没到上限，就新建；如果已经到了 &lt;code&gt;SetMaxOpenConns&lt;/code&gt; 的上限，请求就等。&lt;/p&gt;
&lt;p&gt;这句话很关键。&lt;/p&gt;
&lt;p&gt;一旦你设置了 &lt;code&gt;max_open&lt;/code&gt;，数据库访问就有点像抢信号量。抢不到，就排队。Go 官方文档也提醒过，设置上限后，应用可能等待连接，甚至在某些调用链里把自己等死。&lt;/p&gt;
&lt;p&gt;所以 &lt;code&gt;max_open&lt;/code&gt; 调大，不等于吞吐一定变大。&lt;/p&gt;
&lt;p&gt;它只是允许更多请求同时挤到数据库面前。数据库还有自己的 CPU、IO、锁、连接数上限、慢查询和事务。应用侧看起来“池子不满了”，数据库侧可能已经开始喘不过气。&lt;/p&gt;
&lt;h2 id="先把四个旋钮分清楚"&gt;先把四个旋钮分清楚&lt;/h2&gt;
&lt;p&gt;这几个参数最好不要放在一起背。&lt;/p&gt;
&lt;p&gt;它们管的是四件事。&lt;/p&gt;
&lt;h3 id="setmaxopenconns同时打开多少连接"&gt;&lt;code&gt;SetMaxOpenConns&lt;/code&gt;：同时打开多少连接&lt;/h3&gt;
&lt;p&gt;它决定一个 Go 进程最多能打开多少条数据库连接。&lt;/p&gt;
&lt;p&gt;设成 0，表示不限制。听起来自由，线上通常不自由。因为数据库连接不是免费的，应用实例一多，很容易把数据库总连接预算吃光。&lt;/p&gt;
&lt;p&gt;更稳的做法是反过来算：数据库最多能给业务多少连接？线上有几个应用实例？还要给管理连接、迁移任务、其他服务留多少余量？先分预算，再压测。&lt;/p&gt;
&lt;p&gt;不要从“我的服务需要多少”开始，要从“数据库最多能承受多少”开始。&lt;/p&gt;
&lt;h3 id="setmaxidleconns空闲时留多少连接"&gt;&lt;code&gt;SetMaxIdleConns&lt;/code&gt;：空闲时留多少连接&lt;/h3&gt;
&lt;p&gt;这个参数控制 idle 连接上限。&lt;/p&gt;
&lt;p&gt;它不是浪费。很多线上抖动，就是因为 idle 设得太小，流量一上来就集体建连接。建连本身要握手、认证、初始化会话，有些驱动和数据库还会做额外准备工作。&lt;/p&gt;
&lt;p&gt;默认只保留 2 条 idle 连接。对并发稍微高一点的服务，这个默认值经常太保守。&lt;/p&gt;
&lt;p&gt;但 idle 也不是越多越好。留太多，数据库侧看到的连接数会长期偏高；多实例同时存在时，空闲连接也会堆起来。&lt;/p&gt;</description></item></channel></rss>