From 380033dfe08039b9f70efbd26de068213169e6ca Mon Sep 17 00:00:00 2001 From: ssongliu Date: Wed, 29 Jul 2026 10:42:21 +0800 Subject: [PATCH] fix: support MySQL backup GTID options (#13407) * fix: support MySQL backup GTID options * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- agent/app/model/backup.go | 1 + agent/app/service/backup_mysql.go | 41 +++++++++++++++++++ agent/app/service/cronjob_backup.go | 1 + agent/init/migration/migrate.go | 1 + agent/init/migration/migrations/init.go | 7 ++++ frontend/src/components/backup/index.vue | 6 +-- frontend/src/lang/modules/en.ts | 2 + frontend/src/lang/modules/es-es.ts | 2 + frontend/src/lang/modules/fa.ts | 1 + frontend/src/lang/modules/ja.ts | 2 + frontend/src/lang/modules/ko.ts | 1 + frontend/src/lang/modules/lo.ts | 1 + frontend/src/lang/modules/ms.ts | 2 + frontend/src/lang/modules/pt-br.ts | 2 + frontend/src/lang/modules/ru.ts | 2 + frontend/src/lang/modules/tr.ts | 2 + frontend/src/lang/modules/zh-Hant.ts | 1 + frontend/src/lang/modules/zh.ts | 1 + frontend/src/views/cronjob/cronjob/helper.ts | 8 ++++ .../views/cronjob/cronjob/operate/index.vue | 4 +- 20 files changed, 83 insertions(+), 5 deletions(-) diff --git a/agent/app/model/backup.go b/agent/app/model/backup.go index 392bd7e21..e8d21bd18 100644 --- a/agent/app/model/backup.go +++ b/agent/app/model/backup.go @@ -31,4 +31,5 @@ type BackupRecord struct { Status string `json:"status"` Message string `json:"message"` Description string `json:"description"` + Args string `gorm:"not null;default:''" json:"args"` } diff --git a/agent/app/service/backup_mysql.go b/agent/app/service/backup_mysql.go index 04aa67ab4..187c5c245 100644 --- a/agent/app/service/backup_mysql.go +++ b/agent/app/service/backup_mysql.go @@ -2,6 +2,7 @@ package service import ( "context" + "encoding/json" "fmt" "os" "path" @@ -39,6 +40,7 @@ func (u *BackupService) MysqlBackup(req dto.CommonBackup) error { TaskID: req.TaskID, Status: constant.StatusWaiting, Description: req.Description, + Args: encodeBackupArgs(req.Args), } if err := backupRepo.CreateRecord(record); err != nil { global.LOG.Errorf("save backup record failed, err: %v", err) @@ -143,6 +145,14 @@ func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback if !isRollback { rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout))) + var rollbackArgs []string + if req.BackupRecordID != 0 { + record, err := backupRepo.GetRecord(repo.WithByID(req.BackupRecordID)) + if err != nil { + return err + } + rollbackArgs = decodeBackupArgs(record.Args) + } if err := cli.Backup(client.BackupInfo{ Name: req.DetailName, Type: req.Type, @@ -150,6 +160,7 @@ func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback Format: dbInfo.Format, TargetDir: path.Dir(rollbackFile), FileName: path.Base(rollbackFile), + Args: rollbackArgs, }); err != nil { return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", req.DetailName, err) } @@ -242,6 +253,36 @@ func doMysqlBackup(db DatabaseHelper, targetDir, fileName, secret string) error return nil } +func encodeBackupArgs(args []string) string { + var items []string + for _, arg := range args { + if len(arg) != 0 { + items = append(items, arg) + } + } + if len(items) == 0 { + return "" + } + data, err := json.Marshal(items) + if err != nil { + global.LOG.Warnf("marshal backup args failed: %v", err) + return "" + } + return string(data) +} + +func decodeBackupArgs(value string) []string { + if len(value) == 0 { + return nil + } + var args []string + if err := json.Unmarshal([]byte(value), &args); err != nil { + global.LOG.Warnf("unmarshal backup args failed: %v", err) + return nil + } + return args +} + func loadSqlFile(file string) (string, error) { if !strings.HasSuffix(file, ".tar.gz") && !strings.HasSuffix(file, ".zip") { return file, nil diff --git a/agent/app/service/cronjob_backup.go b/agent/app/service/cronjob_backup.go index 9ee51258b..1cd542d15 100644 --- a/agent/app/service/cronjob_backup.go +++ b/agent/app/service/cronjob_backup.go @@ -163,6 +163,7 @@ func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Ti record.Name = dbInfo.Database record.DetailName = dbInfo.Name record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + record.Args = encodeBackupArgs(dbInfo.Args) backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name)) switch dbInfo.DBType { diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go index e6d934dc6..2685d60c2 100644 --- a/agent/init/migration/migrate.go +++ b/agent/init/migration/migrate.go @@ -93,6 +93,7 @@ func InitAgentDB() { migrations.AddMcpServerGatewayArgs, migrations.InitFirewallPortWhiteList, migrations.AddDatabaseUserTable, + migrations.AddBackupRecordArgs, }) if err := m.Migrate(); err != nil { global.LOG.Error(err) diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go index f31bda168..874387e7b 100644 --- a/agent/init/migration/migrations/init.go +++ b/agent/init/migration/migrations/init.go @@ -1676,3 +1676,10 @@ var AddMcpServerGatewayArgs = &gormigrate.Migration{ return tx.AutoMigrate(&model.McpServer{}) }, } + +var AddBackupRecordArgs = &gormigrate.Migration{ + ID: "20260729-add-backup-record-args", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.BackupRecord{}) + }, +} diff --git a/frontend/src/components/backup/index.vue b/frontend/src/components/backup/index.vue index 30d048f3b..cbe2acea4 100644 --- a/frontend/src/components/backup/index.vue +++ b/frontend/src/components/backup/index.vue @@ -129,9 +129,9 @@ {{ $t('database.mongodbRecoverDropAllCollectionsHelper') }} - + - + {{ item.arg }} {{ item.description }} @@ -199,7 +199,7 @@ import { MsgSuccess } from '@/utils/message'; import TaskLog from '@/components/log/task/index.vue'; import { routerToFileWithPath } from '@/utils/router'; import { useGlobalStore } from '@/composables/useGlobalStore'; -import { mysqlArgs } from '@/views/cronjob/cronjob/helper'; +import { loadMysqlArgs } from '@/views/cronjob/cronjob/helper'; const { currentNode } = useGlobalStore(); const emit = defineEmits(['close']); diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 85b3fbe3d..a5ff75a94 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -1821,6 +1821,8 @@ const message = { singleTransaction: 'Backup InnoDB tables using a single transaction, suitable for large-volume data backups', quick: 'Read data row by row instead of loading the entire table into memory, suitable for large-volume data and low-memory machine backups', skipLockTables: 'Backup without locking all tables, suitable for highly concurrent databases', + setGtidPurgedOff: + 'Do not export GTID information, suitable for database recovery in Group Replication environments', missBackupAccount: 'The backup account could not be found', syncDate: 'Synchronization time ', clean: 'Cache clean', diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts index ad3bf0958..b63f98211 100644 --- a/frontend/src/lang/modules/es-es.ts +++ b/frontend/src/lang/modules/es-es.ts @@ -1855,6 +1855,8 @@ const message = { quick: 'Lee datos fila por fila en lugar de cargar la tabla completa en memoria, adecuado para copias de seguridad de datos de gran volumen y máquinas con poca memoria', skipLockTables: 'Copia de seguridad sin bloquear todas las tablas, adecuada para bases de datos altamente concurrentes', + setGtidPurgedOff: + 'No exportar información GTID, adecuado para restaurar bases de datos en entornos de Group Replication', missBackupAccount: 'No se pudo encontrar la cuenta de respaldo', syncDate: 'Hora de sincronización', clean: 'Limpiar caché', diff --git a/frontend/src/lang/modules/fa.ts b/frontend/src/lang/modules/fa.ts index f9b8e6df4..edb07c590 100644 --- a/frontend/src/lang/modules/fa.ts +++ b/frontend/src/lang/modules/fa.ts @@ -1810,6 +1810,7 @@ const message = { 'پشتیبان‌گیری از جداول InnoDB با استفاده از یک تراکنش واحد، مناسب برای پشتیبان‌گیری داده‌های حجیم', quick: 'خواندن داده‌ها سطر به سطر به جای بارگذاری کل جدول در حافظه، مناسب برای پشتیبان‌گیری داده‌های حجیم و ماشین‌های با حافظه کم', skipLockTables: 'پشتیبان‌گیری بدون قفل کردن همه جداول، مناسب برای پایگاه‌های داده با هم‌روندی بالا', + setGtidPurgedOff: 'اطلاعات GTID صادر نشود؛ مناسب برای بازیابی پایگاه داده در محیط‌های Group Replication', missBackupAccount: 'حساب پشتیبان پیدا نشد', syncDate: 'زمان همگام‌سازی', clean: 'پاک‌سازی کش', diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts index 9826fdecd..03dde3e6e 100644 --- a/frontend/src/lang/modules/ja.ts +++ b/frontend/src/lang/modules/ja.ts @@ -1820,6 +1820,8 @@ const message = { '単一トランザクションを使用して InnoDB テーブルをバックアップします。大容量データのバックアップに適しています', quick: 'テーブル全体をメモリにロードする代わりに、データを行単位で読み取ります。大容量データや低メモリマシンのバックアップに適しています', skipLockTables: 'すべてのテーブルをロックせずにバックアップします。高並列データベースに適しています', + setGtidPurgedOff: + 'バックアップ時に GTID 情報をエクスポートしません。グループレプリケーション環境でのデータベース復元に適しています', missBackupAccount: 'バックアップアカウントは見つかりませんでした', syncDate: '同期時間', clean: 'キャッシュクリーン', diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts index a3cf4f732..fe2fc2092 100644 --- a/frontend/src/lang/modules/ko.ts +++ b/frontend/src/lang/modules/ko.ts @@ -1791,6 +1791,7 @@ const message = { singleTransaction: '단일 트랜잭션을 사용하여 InnoDB 테이블을 백업하며, 대용량 데이터 백업에 적합합니다', quick: '전체 테이블을 메모리에 로드하는 대신 데이터를 행별로 읽습니다. 대용량 데이터 및 저메모리 시스템 백업에 적합합니다', skipLockTables: '모든 테이블을 잠그지 않고 백업합니다. 높은 동시성 데이터베이스에 적합합니다', + setGtidPurgedOff: '백업 시 GTID 정보를 내보내지 않으며 그룹 복제 환경에서 데이터베이스를 복구할 때 적합합니다', missBackupAccount: '백업 계정을 찾을 수 없습니다', syncDate: '동기화 시간', clean: '캐시 정리', diff --git a/frontend/src/lang/modules/lo.ts b/frontend/src/lang/modules/lo.ts index 4cfec0cd9..306ab5bca 100644 --- a/frontend/src/lang/modules/lo.ts +++ b/frontend/src/lang/modules/lo.ts @@ -1789,6 +1789,7 @@ const message = { singleTransaction: 'ສຳຮອງຕາຕະລາງ InnoDB ໂດຍໃຊ້ single transaction, ເໝາະສຳລັບຂໍ້ມູນຂະໜາດໃຫຍ່', quick: 'ອ່ານຂໍ້ມູນເທື່ອລະແຖວແທນການໂຫຼດທັງໝົດລົງ memory, ເໝາະສຳລັບຂໍ້ມູນໃຫຍ່ ແລະ ເຄື່ອງທີ່ມີ memory ຕ່ຳ', skipLockTables: 'ສຳຮອງໂດຍບໍ່ລັອກຕາຕະລາງ, ເໝາະສຳລັບຖານຂໍ້ມູນທີ່ມີການໃຊ້ງານສູງ', + setGtidPurgedOff: 'ບໍ່ສົ່ງອອກຂໍ້ມູນ GTID, ເໝາະສຳລັບການກູ້ຄືນຖານຂໍ້ມູນໃນສະພາບແວດລ້ອມ Group Replication', missBackupAccount: 'ກວດບໍ່ພບບັນຊີສຳຮອງຂໍ້ມູນ', syncDate: 'ເວລາຊິງໂຄໄນ ', clean: 'ລ້າງ Cache', diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts index 92b4fe802..f9a66c423 100644 --- a/frontend/src/lang/modules/ms.ts +++ b/frontend/src/lang/modules/ms.ts @@ -1838,6 +1838,8 @@ const message = { 'Sandaran jadual InnoDB menggunakan transaksi tunggal, sesuai untuk sandaran data isipadu besar', quick: 'Baca data baris demi baris daripada memuatkan keseluruhan jadual ke dalam ingatan, sesuai untuk sandaran data isipadu besar dan mesin ingatan rendah', skipLockTables: 'Sandaran tanpa mengunci semua jadual, sesuai untuk pangkalan data konkuren tinggi', + setGtidPurgedOff: + 'Jangan eksport maklumat GTID, sesuai untuk pemulihan pangkalan data dalam persekitaran Group Replication', missBackupAccount: 'Akaun sandaran tidak dijumpai', syncDate: 'Waktu penyelarasan', clean: 'Bersihkan cache', diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts index 1878a1e00..30a91f35e 100644 --- a/frontend/src/lang/modules/pt-br.ts +++ b/frontend/src/lang/modules/pt-br.ts @@ -1840,6 +1840,8 @@ const message = { 'Faz backup de tabelas InnoDB usando uma única transação, adequado para backups de dados de grande volume', quick: 'Lê dados linha por linha em vez de carregar toda a tabela na memória, adequado para backups de dados de grande volume e máquinas com pouca memória', skipLockTables: 'Backup sem bloquear todas as tabelas, adequado para bancos de dados altamente concorrentes', + setGtidPurgedOff: + 'Não exportar informações de GTID, adequado para restaurar bancos de dados em ambientes de Group Replication', missBackupAccount: 'A conta de backup não foi encontrada', syncDate: 'Data de sincronização', clean: 'Limpeza de cache', diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts index b4e771271..9d5287f0e 100644 --- a/frontend/src/lang/modules/ru.ts +++ b/frontend/src/lang/modules/ru.ts @@ -1827,6 +1827,8 @@ const message = { 'Резервное копирование таблиц InnoDB с использованием одной транзакции, подходит для резервного копирования данных большого объема', quick: 'Чтение данных построчно вместо загрузки всей таблицы в память, подходит для резервного копирования данных большого объема и машин с низкой памятью', skipLockTables: 'Резервное копирование без блокировки всех таблиц, подходит для высококонкурентных баз данных', + setGtidPurgedOff: + 'Не экспортировать информацию GTID, подходит для восстановления базы данных в среде Group Replication', missBackupAccount: 'Не удалось найти учетную запись резервного копирования', syncDate: 'Время синхронизации', clean: 'Очистка кэша', diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts index 6c269eff4..f459938b5 100644 --- a/frontend/src/lang/modules/tr.ts +++ b/frontend/src/lang/modules/tr.ts @@ -1839,6 +1839,8 @@ const message = { 'InnoDB tablolarını tek bir işlem kullanarak yedekler, büyük hacimli veri yedeklemeleri için uygundur', quick: 'Tüm tabloyu belleğe yüklemek yerine verileri satır satır okur, büyük hacimli veri ve düşük bellekli makine yedeklemeleri için uygundur', skipLockTables: 'Tüm tabloları kilitlemeden yedekleme, yüksek eşzamanlılığa sahip veritabanları için uygundur', + setGtidPurgedOff: + 'GTID bilgilerini dışa aktarmayın; Group Replication ortamlarında veritabanı kurtarma için uygundur', missBackupAccount: 'Yedekleme hesabı bulunamadı', syncDate: 'Senkronizasyon zamanı ', clean: 'Önbellek temizleme', diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts index fe4a34bea..0a9ef0bf1 100644 --- a/frontend/src/lang/modules/zh-Hant.ts +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -1713,6 +1713,7 @@ const message = { singleTransaction: '使用單一事務備份 InnoDB 表,適用於大資料量的備份', quick: '逐行讀取資料,而不是將整個表載入到記憶體中,適用於大資料量和低記憶體機器的備份', skipLockTables: '不鎖定所有表進行備份,適用於高併發的資料庫', + setGtidPurgedOff: '備份時不匯出 GTID 資訊,適用於群組複寫環境中的資料庫還原', missBackupAccount: '未能找到備份帳號', syncDate: '同步時間 ', clean: '快取清理', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index f461171e0..4ea2e1959 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -1723,6 +1723,7 @@ const message = { singleTransaction: '使用单一事务备份 InnoDB 表,适用于大数据量的备份', quick: '逐行读取数据,而不是将整个表加载到内存中,适用于大数据量和低内存机器的备份', skipLockTables: '不锁定所有表进行备份,适用于高并发的数据库', + setGtidPurgedOff: '备份时不导出 GTID 信息,适用于组复制环境中的数据库恢复', missBackupAccount: '未能找到备份账号', syncDate: '同步时间 ', clean: '缓存清理', diff --git a/frontend/src/views/cronjob/cronjob/helper.ts b/frontend/src/views/cronjob/cronjob/helper.ts index 46ae342fa..85465e4f8 100644 --- a/frontend/src/views/cronjob/cronjob/helper.ts +++ b/frontend/src/views/cronjob/cronjob/helper.ts @@ -24,7 +24,15 @@ export const mysqlArgs = [ { arg: '--single-transaction', description: i18n.global.t('cronjob.singleTransaction') }, { arg: '--quick', description: i18n.global.t('cronjob.quick') }, { arg: '--skip-lock-tables', description: i18n.global.t('cronjob.skipLockTables') }, + { + arg: '--set-gtid-purged=OFF', + description: i18n.global.t('cronjob.setGtidPurgedOff'), + supportedTypes: ['mysql', 'mysql-cluster'], + }, ]; +export const loadMysqlArgs = (dbType: string) => { + return mysqlArgs.filter((item) => !item.supportedTypes || item.supportedTypes.includes(dbType)); +}; function loadWeek(i: number) { for (const week of weekOptions) { if (week.value === i) { diff --git a/frontend/src/views/cronjob/cronjob/operate/index.vue b/frontend/src/views/cronjob/cronjob/operate/index.vue index c45946e99..131f25f84 100644 --- a/frontend/src/views/cronjob/cronjob/operate/index.vue +++ b/frontend/src/views/cronjob/cronjob/operate/index.vue @@ -356,7 +356,7 @@