症状
遇到“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');
}
验证
模拟重启并确认连接池重连且应用恢复;验证写入针对主库端点成功。
修复后补充一条回归测试,复现原始症状,防止问题再次出现。
这类故障通常是环境问题而非纯逻辑错误,因此在缺陷记录中应写明完整命令、环境变量与工作目录。请用最小的脚本复现,待最小用例清楚后再扩大范围。为这类故障准备一份简短的排查清单,能显著减少下次排障时间。