前言
往常,我们自己开发的系统,通常由运维进行数据库的备份。但这一次,我开发的一个小东西是交给客户自己去使用的。给用户系统搞好之后,用户希望可以在管理后台进行数据库的备份操作。所以,我需要一个接口,来实现数据库的备份。
一开始,我希望使用 mysql 数据库自带的 mysqldump
命令来实现备份,然后用 node 调用系统命令来实现。原理上是没有问题的,实践过程中遇到一些小的问题,就是 mysql 数据库的服务器和代码运行的服务器不是同一台服务器。
所以,可以不可以直接通过 SQL 语言来进行数据库的备份呢?虽然我作为一个前端,对于 SQL 肯定是不精通的。但是,借助 DeepSeek,我相信这个问题很好解决。
完整代码演示
import * as fs from 'node:fs/promises'
import * as path from 'node:path'
import * as mysql from 'mysql2/promise'
interface BackupConfig {
host: string
user: string
password: string
database: string
outputDir?: string
}
export class MySQLBackup {
private connection: mysql.Connection | null = null
constructor(private config: BackupConfig) {
this.config.outputDir = config.outputDir || './backups'
}
async connect() {
this.connection = await mysql.createConnection({
host: this.config.host,
user: this.config.user,
password: this.config.password,
database: this.config.database,
})
}
async getTableNames(): Promise<string[]> {
const [rows] = await this.connection.query(
`SELECT table_name FROM information_schema.tables
WHERE table_schema = ?`,
[this.config.database],
)
return (rows as any[]).map((row) => row.TABLE_NAME)
}
async backupTable(table: string): Promise<string> {
// 获取时区设置
const [timezone] = await this.connection.query('SELECT @@session.time_zone AS tz')
const tz = (timezone as any)[0].tz || '+00:00'
// 获取建表语句
const [createTable] = await this.connection.query(`SHOW CREATE TABLE \`${table}\``)
// 获取数据并格式化为标准SQL
const [rows] = await this.connection.query(`SELECT * FROM \`${table}\``)
let sql = `SET time_zone = '${tz}';\n\n`
sql += `-- DROP TABLE IF EXISTS \`${table}\`;\n`
sql += `${(createTable as any)[0]['Create Table']};\n\n`
if ((rows as any[]).length > 0) {
sql += `INSERT INTO \`${table}\` VALUES\n`
sql += `${(rows as any[])
.map((row) => {
const values = Object.values(row)
.map((v) => {
if (v === null) return 'NULL'
if (v instanceof Date) {
// 使用时区修正后的本地时间
const adjustedDate = new Date(v.getTime() - v.getTimezoneOffset() * 60000)
return `'${adjustedDate.toISOString().slice(0, 19).replace('T', ' ')}'`
}
if (typeof v === 'string') return mysql.escape(v)
return v
})
.join(',')
return `(${values})`
})
.join(',\n')};\n\n`
}
return sql
}
async fullBackup() {
try {
await fs.mkdir(this.config.outputDir, { recursive: true })
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const backupFile = path.join(this.config.outputDir, `backup_${timestamp}.sql`)
const tables = await this.getTableNames()
let backupContent = `-- MySQL Backup\n-- Database: ${this.config.database}\n-- Timestamp: ${timestamp}\n\n`
for (const table of tables) {
backupContent += await this.backupTable(table)
}
await fs.writeFile(backupFile, backupContent)
console.log(`Backup saved to: ${backupFile}`)
} catch (error) {
console.error('Backup failed:', error)
} finally {
if (this.connection) await this.connection.end()
}
}
}
export default MySQLBackup
// 使用示例
// ;(async () => {
// const backup = new MySQLBackup({
// host: 'localhost',
// user: 'root',
// password: 'yourpassword',
// database: 'your_database',
// })
// await backup.connect()
// await backup.fullBackup()
// })()
小结
DeepSeek 提供的第一版代码直接报错,在告诉 DeepSeek 出错在哪里之后,新给了调整后的代码,终于顺利的备份了数据库了。
但是,我拿备份的 SQL 文件进行导入时发现,其在处理特殊字符和日期格式上出现了问题,于是,把问题再次交给 DeepSeek,又优化了一版本,这一次,终于备份成功,并且导入成功了。
在数据库导入成功之后,我仔细检查数据是否有异常,结果很快发现了异常,就是,数据库中的时间,和原始数据的时间,之间差了8个小时。很显然,这是 DeepSeek 没有考虑时区导致的。
于是,再次给 DeepSeek 讲,关于时区的问题,最终给出了以上代码,我实测,成功备份数据库,并顺利导入新数据库,仔细看数据,没有再发现新的问题。
其中 backupTable
函数,我基本不太理解,如果还有优化空间,或者BUG修复,欢迎各位看官在评论中留言交流。
最后,这种数据库备份的代码,只能用于数据库内容不大的小项目,如果数据库内容较多,还是很不合适的。那种情况,交给运维去处理或者借助云服务商的备份工具会更合理。
好,看到最后了,各位看官点个赞吧!谢谢!