mirror of
				https://gitee.com/dromara/mayfly-go
				synced 2025-11-04 08:20:25 +08:00 
			
		
		
		
	!87 fix: 修复数据库备份与恢复问题
* feat: 修复数据库备份与恢复问题 * feat: 启用 BINLOG 支持全量备份和增量备份,未启用 BINLOG 仅支持全量备份 * feat: 数据库恢复后自动备份,避免数据丢失
This commit is contained in:
		@@ -77,6 +77,15 @@ func (svc *DbProgramMysql) GetBinlogFilePath(fileName string) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbBackupHistory) (*entity.BinlogInfo, error) {
 | 
			
		||||
	binlogEnabled, err := svc.CheckBinlogEnabled(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	rowFormatEnabled, err := svc.CheckBinlogRowFormat(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir := svc.getDbBackupDir(backupHistory.DbInstanceId, backupHistory.DbBackupId)
 | 
			
		||||
	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -95,10 +104,11 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
 | 
			
		||||
		"--add-drop-database",
 | 
			
		||||
		"--result-file", tmpFile,
 | 
			
		||||
		"--single-transaction",
 | 
			
		||||
		"--master-data=2",
 | 
			
		||||
		"--databases", backupHistory.DbName,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if binlogEnabled && rowFormatEnabled {
 | 
			
		||||
		args = append(args, "--master-data=2")
 | 
			
		||||
	}
 | 
			
		||||
	cmd := exec.CommandContext(ctx, svc.getMysqlBin().MysqldumpPath, args...)
 | 
			
		||||
	logx.Debugf("backup database using mysqldump binary: %s", cmd.String())
 | 
			
		||||
	if err := runCmd(cmd); err != nil {
 | 
			
		||||
@@ -115,7 +125,10 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	binlogInfo, err := readBinlogInfoFromBackup(reader)
 | 
			
		||||
	binlogInfo := &entity.BinlogInfo{}
 | 
			
		||||
	if binlogEnabled && rowFormatEnabled {
 | 
			
		||||
		binlogInfo, err = readBinlogInfoFromBackup(reader)
 | 
			
		||||
	}
 | 
			
		||||
	_ = reader.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrapf(err, "从备份文件中读取 binlog 信息失败")
 | 
			
		||||
@@ -568,18 +581,24 @@ func (svc *DbProgramMysql) ReplayBinlog(ctx context.Context, originalDatabase, t
 | 
			
		||||
		return errors.Wrap(err, "启动 mysqlbinlog 程序失败")
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = mysqlbinlogCmd.Cancel()
 | 
			
		||||
		if err := mysqlbinlogCmd.Wait(); err != nil {
 | 
			
		||||
			if replayErr != nil {
 | 
			
		||||
				replayErr = errors.Wrap(replayErr, "运行 mysqlbinlog 程序失败")
 | 
			
		||||
			} else {
 | 
			
		||||
				replayErr = errors.Errorf("运行 mysqlbinlog 程序失败: %s", mysqlbinlogErr.String())
 | 
			
		||||
			if mysqlbinlogErr.Len() > 0 {
 | 
			
		||||
				logx.Errorf("运行 mysqlbinlog 程序失败", mysqlbinlogErr.String())
 | 
			
		||||
				if replayErr != nil {
 | 
			
		||||
					replayErr = errors.Wrap(replayErr, "运行 mysqlbinlog 程序失败: "+mysqlbinlogErr.String())
 | 
			
		||||
				} else {
 | 
			
		||||
					replayErr = errors.Errorf("运行 mysqlbinlog 程序失败: %s", mysqlbinlogErr.String())
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	if err := mysqlCmd.Start(); err != nil {
 | 
			
		||||
		logx.Error("启动 mysql 程序失败")
 | 
			
		||||
		return errors.Wrap(err, "启动 mysql 程序失败")
 | 
			
		||||
	}
 | 
			
		||||
	if err := mysqlCmd.Wait(); err != nil {
 | 
			
		||||
		logx.Errorf("运行 mysql 程序失败: %s", mysqlErr.String())
 | 
			
		||||
		return errors.Errorf("运行 mysql 程序失败: %s", mysqlErr.String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -606,27 +625,30 @@ func (svc *DbProgramMysql) getServerVariable(_ context.Context, varName string)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBinlogEnabled checks whether binlog is enabled for the current instance.
 | 
			
		||||
func (svc *DbProgramMysql) CheckBinlogEnabled(ctx context.Context) error {
 | 
			
		||||
func (svc *DbProgramMysql) CheckBinlogEnabled(ctx context.Context) (bool, error) {
 | 
			
		||||
	value, err := svc.getServerVariable(ctx, "log_bin")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	switch {
 | 
			
		||||
	case err == nil:
 | 
			
		||||
		return strings.ToUpper(value) == "ON", nil
 | 
			
		||||
	case errors.Is(err, sql.ErrNoRows):
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	if strings.ToUpper(value) != "ON" {
 | 
			
		||||
		return errors.Errorf("数据库未启用 binlog")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckBinlogRowFormat checks whether the binlog format is ROW.
 | 
			
		||||
func (svc *DbProgramMysql) CheckBinlogRowFormat(ctx context.Context) error {
 | 
			
		||||
// CheckBinlogRowFormat checks whether the binlog format is ROW or MIXED.
 | 
			
		||||
func (svc *DbProgramMysql) CheckBinlogRowFormat(ctx context.Context) (bool, error) {
 | 
			
		||||
	value, err := svc.getServerVariable(ctx, "binlog_format")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	switch {
 | 
			
		||||
	case err == nil:
 | 
			
		||||
		value = strings.ToUpper(value)
 | 
			
		||||
		return value == "ROW" || value == "MIXED", nil
 | 
			
		||||
	case errors.Is(err, sql.ErrNoRows):
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	if strings.ToUpper(value) != "ROW" {
 | 
			
		||||
		return errors.Errorf("binlog 格式 %s 不是行模式", value)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runCmd(cmd *exec.Cmd) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -153,8 +153,9 @@ func (s *DbInstanceSuite) TestRestorePontInTime() {
 | 
			
		||||
	s.createTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
			
		||||
	time.Sleep(time.Second)
 | 
			
		||||
	targetTime := time.Now()
 | 
			
		||||
 | 
			
		||||
	// 首次恢复数据库
 | 
			
		||||
	firstTargetTime := time.Now()
 | 
			
		||||
	s.dropTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "运行 mysql 程序失败")
 | 
			
		||||
	s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
			
		||||
@@ -165,7 +166,28 @@ func (s *DbInstanceSuite) TestRestorePontInTime() {
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "运行 mysql 程序失败")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
			
		||||
 | 
			
		||||
	s.testReplayBinlog(backupHistory, targetTime)
 | 
			
		||||
	s.testReplayBinlog(backupHistory, firstTargetTime)
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
			
		||||
 | 
			
		||||
	s.testBackup(backupHistory)
 | 
			
		||||
 | 
			
		||||
	// 再次恢复数据库
 | 
			
		||||
	secondTargetTime := time.Now()
 | 
			
		||||
	s.dropTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "运行 mysql 程序失败")
 | 
			
		||||
	s.dropTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "运行 mysql 程序失败")
 | 
			
		||||
	s.createTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "")
 | 
			
		||||
 | 
			
		||||
	s.testRestore(backupHistory)
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
			
		||||
 | 
			
		||||
	s.testReplayBinlog(backupHistory, secondTargetTime)
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameBackupTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameRestorePITTest, "")
 | 
			
		||||
	s.selectTable(dbNameBackupTest, tableNameNoBackupTest, "运行 mysql 程序失败")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user