
本文介绍如何在 node.js 环境中使用 typescript 或 javascript 编写具备自动重试能力的 mysql 数据库连接与查询逻辑,支持可配置最大重试次数与间隔,并确保连接失败时不崩溃、不提前返回,符合生产级容错要求。
在构建高可用后端服务时,数据库临时不可用(如维护重启、网络抖动、容器启动延迟)是常见场景。若连接逻辑缺乏重试机制,应用可能快速失败甚至退出。原问题中提供的递归 setTimeout + 异步回调方案存在明显缺陷:无法控制重试上限、无法统一错误处理、易导致调用栈爆炸或内存泄漏,且 query 方法未集成重试逻辑,违背“失败即重试”的设计目标。
以下是一个专业、可维护、生产就绪的重试实现方案,核心改进包括:
- ✅ 分离关注点:将通用重试逻辑抽象为独立的 retry 工具方法,支持任意 Promise 返回函数;
- ✅ 可控重试策略:支持最大重试次数(maxRetries)和固定延迟(delay),负值表示无限重试;
- ✅ 连接阶段强保障:connect() 方法内部封装连接过程为 Promise,并通过 retry() 保证最终成功或明确超限失败;
- ✅ 查询阶段按需重试:query() 本身不内置重试(避免无界重试掩盖 SQL 错误),但可轻松组合 retry() 实现语义化重试(见后文示例);
- ✅ 类型安全与可读性:使用现代 async/await + Promise 风格,规避回调地狱,便于 TypeScript 类型推导。
✅ 推荐实现(TypeScript 兼容)
import mysql from 'mysql';
interface DbConfig {
host: string;
user: string;
password: string;
port: string | number;
database?: string;
}
class Database {
connection: mysql.Connection | null = null;
config?: DbConfig;
// 通用重试工具:适用于任意返回 Promise 的操作
retry<T>(
fn: () => Promise<T>,
maxRetries: number,
delay: number
): Promise<T> {
return new Promise((resolve, reject) => {
let attempts = 0;
const attempt = () => {
fn()
.then(result => resolve(result))
.catch(error => {
attempts++;
if (maxRetries < 0 || attempts <= maxRetries) {
console.warn(
`[DB Retry] Attempt ${attempts}/${maxRetries === -1 ? '∞' : maxRetries} failed:`,
error instanceof Error ? error.message : String(error)
);
setTimeout(attempt, delay);
} else {
reject(
new Error(
`Max retries (${maxRetries}) exceeded. Last error: ${
error instanceof Error ? error.message : String(error)
}`
)
);
}
});
};
attempt();
});
}
// 建立带重试的数据库连接
async connect(config: DbConfig): Promise<void> {
const connectOnce = (): Promise<mysql.Connection> => {
return new Promise((resolve, reject) => {
const conn = mysql.createConnection(config);
conn.connect(err => {
if (err) reject(err);
else resolve(conn);
});
});
};
try {
const conn = await this.retry(
connectOnce,
parseInt(process.env.DB_MAX_RETRY || '5', 10),
parseInt(process.env.DB_RETRY_INTERVAL || '5000', 10)
);
this.connection = conn;
this.config = config;
console.log(`✅ Database connected: ${config.database || '(no DB name)'}`);
} catch (err) {
console.error('❌ Failed to establish database connection after retries:', err);
throw err; // 让调用方决定是否兜底(如降级、告警)
}
}
// 基础查询方法(无重试)——保持职责单一
async query(sql: string, values?: any[]): Promise<any[]> {
if (!this.connection) {
throw new Error('Database not connected. Call connect() first.');
}
return new Promise((resolve, reject) => {
this.connection!.query(sql, values, (err, results) => {
if (err) reject(err);
else resolve(results);
});
});
}
// 可选:封装带重试的查询(适用于幂等读操作)
async queryWithRetry(
sql: string,
values?: any[],
maxRetries = 3,
delay = 2000
): Promise<any[]> {
return this.retry(
() => this.query(sql, values),
maxRetries,
delay
);
}
// 安全关闭连接
close(): void {
if (this.connection) {
this.connection.end();
this.connection = null;
console.log('? Database connection closed.');
}
}
}
export default Database;? 使用示例
import Database from './Database';
const db = new Database();
// 启动时自动重连(阻塞式初始化)
async function initDatabase() {
const config = {
host: process.env.DB_HOST || 'localhost',
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT || 3306,
database: process.env.DB_NAME,
};
console.log('⏳ Initializing database connection...');
try {
await db.connect(config);
console.log('? Database ready.');
// 示例:带重试的健康检查查询
const result = await db.queryWithRetry('SELECT 1 AS alive', 3, 1000);
console.log('Health check:', result);
} catch (error) {
console.error('? Critical: DB initialization failed.', error);
process.exit(1); // 或触发熔断/告警
}
}
initDatabase();⚠️ 关键注意事项
- 环境变量校验:务必在启动前校验 DB_MAX_RETRY 和 DB_RETRY_INTERVAL,避免 NaN 导致无限重试;
- 连接泄漏防护:每次 createConnection 失败后,旧连接若已部分建立需手动 .end() 或 .destroy()(本例中因连接未成功,无需显式销毁);
- 幂等性原则:仅对 SELECT 等安全操作启用查询重试;INSERT/UPDATE 等非幂等操作重试可能导致数据异常,应结合事务与唯一约束保障;
- 监控与告警:在 retry 的 console.warn 中加入指标上报(如 Prometheus Counter),便于观测重试频次;
- 替代方案建议:生产环境推荐使用 mysql2(支持 Promise API 和连接池)+ generic-pool,进一步提升稳定性和性能。
通过以上设计,你将获得一个清晰、可测试、可扩展且符合 Node.js 最佳实践的数据库重试方案,既解决启动期连接韧性问题,也为运行时查询容错提供灵活基础。










