← 中文资源

简体中文版本

MySQL 连接丢失 / 只读 — 故障切换或重启

通过区分空闲超时、重启与故障切换到只读副本,诊断 “MySQL server has gone away” 与只读错误。

阅读英文原文

症状

遇到“MySQL 连接丢失 / 只读 — 故障切换或重启”时靠猜测只会浪费时间。请按证据排查:先复现症状,再执行针对性诊断,定位根因后修复,并用明确检查验证结果。

可观察到的症状包括:

  • 空闲一段时间后出现 “MySQL server has gone away”(错误 2006)
  • 写入失败并提示服务器以 –read-only 运行
  • 高负载下连接成批断开

诊断

先在隔离环境中复现故障,再按顺序执行以下检查,逐步缩小范围。

步骤 1

mysql -e "SHOW VARIABLES LIKE 'wait_timeout'; SHOW GLOBAL STATUS LIKE 'Uptime'"

较短的 wait_timeout 与较低的上线时长可解释空闲断连与近期重启。

步骤 2

mysql -e "SELECT @@read_only, @@super_read_only"

read_only=1 表示该服务器是副本;写入必须指向主库。

常见诱因(按出现频率排序):

  • 连接超过 wait_timeout 被服务端关闭
  • 会话期间服务端重启(崩溃、部署或维护)
  • 故障切换把写入导向了只读副本

根因

客户端持有的连接可能被服务端独立关闭(空闲超时、重启或故障切换),与业务逻辑无关。只读错误表明故障切换后连接落在副本上,而非权限问题。

修复

启用带校验与短最大生命周期的连接池,使用 TCP keepalive 或定期 ping,遇到 2006 时重连重试,并把写入路由到主库端点。

注意:盲目重试非幂等写入会重复数据;仅重试幂等语句,或使用事务与合适的隔离级别。

// Node mysql2 pool with validation and lifetime control
const pool = mysql.createPool({
  host: process.env.DB_HOST,
  waitForConnections: true,
  connectionLimit: 10,
  enableKeepAlive: true,
  keepAliveInitialDelay: 10000,
  maxIdle: 10,
  idleTimeout: 60000,
});

// retry only on connection-loss errors
async function write(sql, params, tries = 3) {
  for (let i = 1; i <= tries; i++) {
    try { return await pool.execute(sql, params); }
    catch (e) {
      if (e.code === 'PROTOCOL_CONNECTION_LOST' || e.code === 'ECONNRESET') continue;
      throw e;
    }
  }
  throw new Error('write failed after retries');
}

验证

模拟重启并确认连接池重连且应用恢复;验证写入针对主库端点成功。

修复后补充一条回归测试,复现原始症状,防止问题再次出现。

这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。