mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
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>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
parent
97d383ed12
commit
380033dfe0
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -93,6 +93,7 @@ func InitAgentDB() {
|
||||
migrations.AddMcpServerGatewayArgs,
|
||||
migrations.InitFirewallPortWhiteList,
|
||||
migrations.AddDatabaseUserTable,
|
||||
migrations.AddBackupRecordArgs,
|
||||
})
|
||||
if err := m.Migrate(); err != nil {
|
||||
global.LOG.Error(err)
|
||||
|
||||
@@ -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{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -129,9 +129,9 @@
|
||||
</el-checkbox>
|
||||
<span class="input-help">{{ $t('database.mongodbRecoverDropAllCollectionsHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="supportMysqlBackupArgs()" :label="$t('cronjob.backupArgs')">
|
||||
<el-form-item v-if="isBackup && supportMysqlBackupArgs()" :label="$t('cronjob.backupArgs')">
|
||||
<el-select v-model="args" filterable allow-create multiple>
|
||||
<el-option v-for="item in mysqlArgs" :key="item.arg" :value="item.arg" :label="item.arg">
|
||||
<el-option v-for="item in loadMysqlArgs(type)" :key="item.arg" :value="item.arg" :label="item.arg">
|
||||
{{ item.arg }}
|
||||
<span class="ml-2">{{ item.description }}</span>
|
||||
</el-option>
|
||||
@@ -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']);
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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é',
|
||||
|
||||
@@ -1810,6 +1810,7 @@ const message = {
|
||||
'پشتیبانگیری از جداول InnoDB با استفاده از یک تراکنش واحد، مناسب برای پشتیبانگیری دادههای حجیم',
|
||||
quick: 'خواندن دادهها سطر به سطر به جای بارگذاری کل جدول در حافظه، مناسب برای پشتیبانگیری دادههای حجیم و ماشینهای با حافظه کم',
|
||||
skipLockTables: 'پشتیبانگیری بدون قفل کردن همه جداول، مناسب برای پایگاههای داده با همروندی بالا',
|
||||
setGtidPurgedOff: 'اطلاعات GTID صادر نشود؛ مناسب برای بازیابی پایگاه داده در محیطهای Group Replication',
|
||||
missBackupAccount: 'حساب پشتیبان پیدا نشد',
|
||||
syncDate: 'زمان همگامسازی',
|
||||
clean: 'پاکسازی کش',
|
||||
|
||||
@@ -1820,6 +1820,8 @@ const message = {
|
||||
'単一トランザクションを使用して InnoDB テーブルをバックアップします。大容量データのバックアップに適しています',
|
||||
quick: 'テーブル全体をメモリにロードする代わりに、データを行単位で読み取ります。大容量データや低メモリマシンのバックアップに適しています',
|
||||
skipLockTables: 'すべてのテーブルをロックせずにバックアップします。高並列データベースに適しています',
|
||||
setGtidPurgedOff:
|
||||
'バックアップ時に GTID 情報をエクスポートしません。グループレプリケーション環境でのデータベース復元に適しています',
|
||||
missBackupAccount: 'バックアップアカウントは見つかりませんでした',
|
||||
syncDate: '同期時間',
|
||||
clean: 'キャッシュクリーン',
|
||||
|
||||
@@ -1791,6 +1791,7 @@ const message = {
|
||||
singleTransaction: '단일 트랜잭션을 사용하여 InnoDB 테이블을 백업하며, 대용량 데이터 백업에 적합합니다',
|
||||
quick: '전체 테이블을 메모리에 로드하는 대신 데이터를 행별로 읽습니다. 대용량 데이터 및 저메모리 시스템 백업에 적합합니다',
|
||||
skipLockTables: '모든 테이블을 잠그지 않고 백업합니다. 높은 동시성 데이터베이스에 적합합니다',
|
||||
setGtidPurgedOff: '백업 시 GTID 정보를 내보내지 않으며 그룹 복제 환경에서 데이터베이스를 복구할 때 적합합니다',
|
||||
missBackupAccount: '백업 계정을 찾을 수 없습니다',
|
||||
syncDate: '동기화 시간',
|
||||
clean: '캐시 정리',
|
||||
|
||||
@@ -1789,6 +1789,7 @@ const message = {
|
||||
singleTransaction: 'ສຳຮອງຕາຕະລາງ InnoDB ໂດຍໃຊ້ single transaction, ເໝາະສຳລັບຂໍ້ມູນຂະໜາດໃຫຍ່',
|
||||
quick: 'ອ່ານຂໍ້ມູນເທື່ອລະແຖວແທນການໂຫຼດທັງໝົດລົງ memory, ເໝາະສຳລັບຂໍ້ມູນໃຫຍ່ ແລະ ເຄື່ອງທີ່ມີ memory ຕ່ຳ',
|
||||
skipLockTables: 'ສຳຮອງໂດຍບໍ່ລັອກຕາຕະລາງ, ເໝາະສຳລັບຖານຂໍ້ມູນທີ່ມີການໃຊ້ງານສູງ',
|
||||
setGtidPurgedOff: 'ບໍ່ສົ່ງອອກຂໍ້ມູນ GTID, ເໝາະສຳລັບການກູ້ຄືນຖານຂໍ້ມູນໃນສະພາບແວດລ້ອມ Group Replication',
|
||||
missBackupAccount: 'ກວດບໍ່ພບບັນຊີສຳຮອງຂໍ້ມູນ',
|
||||
syncDate: 'ເວລາຊິງໂຄໄນ ',
|
||||
clean: 'ລ້າງ Cache',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1827,6 +1827,8 @@ const message = {
|
||||
'Резервное копирование таблиц InnoDB с использованием одной транзакции, подходит для резервного копирования данных большого объема',
|
||||
quick: 'Чтение данных построчно вместо загрузки всей таблицы в память, подходит для резервного копирования данных большого объема и машин с низкой памятью',
|
||||
skipLockTables: 'Резервное копирование без блокировки всех таблиц, подходит для высококонкурентных баз данных',
|
||||
setGtidPurgedOff:
|
||||
'Не экспортировать информацию GTID, подходит для восстановления базы данных в среде Group Replication',
|
||||
missBackupAccount: 'Не удалось найти учетную запись резервного копирования',
|
||||
syncDate: 'Время синхронизации',
|
||||
clean: 'Очистка кэша',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -1713,6 +1713,7 @@ const message = {
|
||||
singleTransaction: '使用單一事務備份 InnoDB 表,適用於大資料量的備份',
|
||||
quick: '逐行讀取資料,而不是將整個表載入到記憶體中,適用於大資料量和低記憶體機器的備份',
|
||||
skipLockTables: '不鎖定所有表進行備份,適用於高併發的資料庫',
|
||||
setGtidPurgedOff: '備份時不匯出 GTID 資訊,適用於群組複寫環境中的資料庫還原',
|
||||
missBackupAccount: '未能找到備份帳號',
|
||||
syncDate: '同步時間 ',
|
||||
clean: '快取清理',
|
||||
|
||||
@@ -1723,6 +1723,7 @@ const message = {
|
||||
singleTransaction: '使用单一事务备份 InnoDB 表,适用于大数据量的备份',
|
||||
quick: '逐行读取数据,而不是将整个表加载到内存中,适用于大数据量和低内存机器的备份',
|
||||
skipLockTables: '不锁定所有表进行备份,适用于高并发的数据库',
|
||||
setGtidPurgedOff: '备份时不导出 GTID 信息,适用于组复制环境中的数据库恢复',
|
||||
missBackupAccount: '未能找到备份账号',
|
||||
syncDate: '同步时间 ',
|
||||
clean: '缓存清理',
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -356,7 +356,7 @@
|
||||
<el-form-item :label="$t('cronjob.backupArgs')">
|
||||
<el-select v-model="form.argItems" filterable allow-create multiple>
|
||||
<el-option
|
||||
v-for="item in mysqlArgs"
|
||||
v-for="item in loadMysqlArgs(form.dbType)"
|
||||
:key="item.arg"
|
||||
:value="item.arg"
|
||||
:label="item.arg"
|
||||
@@ -860,7 +860,7 @@ import {
|
||||
transSpecToObj,
|
||||
weekOptions,
|
||||
cronjobTypes,
|
||||
mysqlArgs,
|
||||
loadMysqlArgs,
|
||||
} from '../helper';
|
||||
import { loadUsers } from '@/api/modules/toolbox';
|
||||
import { loadContainerUsers } from '@/api/modules/container';
|
||||
|
||||
Reference in New Issue
Block a user