大数据量快速数据库还原程序

        最近在搞数据库备份,备份出来的sql都十几G,还原的贼慢还容易报错,就写了个Java程序来定时还原数据库,我加快还原的方法是修改数据库配置,因此程序需要重启数据库,线上项目数据库不能重启的就别用了。(加快后一小时差不多还原20G)

1.创建还原数据库的service类

/*** 用cmd命令将sql文件还原数据库servic类*/
@Component
@Slf4j
public class DataBaseRestoreByCmdService {//需要还原的数据表名组成的集合public static List<String> db_names = new ArrayList<>(Arrays.asList("demo1", "demo2", "demo3", "demo4"));public void initiateRestore(String sqlPackagePath) {// 拼接 SQL 文件完整路径LocalDate now = LocalDate.now();int lastMonth = (now.getMonthValue() - 1);lastMonth = (lastMonth == 0) ? 12 : lastMonth;String lastMonthDate = now.getYear() + "_" + lastMonth;List<CompletableFuture<Void>> futures = new ArrayList<>();for (String dbName : db_names) {String sqlFileName = dbName + "_" + lastMonthDate;String sqlFilePath = sqlPackagePath + sqlFileName + ".sql";//执行数据还原CompletableFuture<Void> future = CompletableFuture.runAsync(() -> SqlImportUtil.exportSql(sqlFilePath));futures.add(future);}// 等待所有任务完成CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));// 处理完成时的操作allOf.thenRun(() -> {// 任务完成后的操作,例如打印日志等log.info("数据库还原完成");}).join(); // 阻塞直到所有任务完成}
}
  • 因为还原多个数据表,我就让他异步执行数据库还原

        最早我是想用ScriptUtils执行sql文件,但是这种方法由于是将sql文件直接读取到内存,因此只能用来运行小的sql,大数据量会导致内存溢出

/*** 用ScriptUtils执行sql文件还原数据库servic类*/
@Component
@Slf4j
public class DatabaseRestoreService {//需要还原的数据表名组成的集合public static List<String> db_names = new ArrayList<>(Arrays.asList("demo1", "demo2", "demo3", "demo4"));private final JdbcTemplate jdbcTemplate;@Autowiredpublic DatabaseRestoreService(JdbcTemplate jdbcTemplate) {this.jdbcTemplate = jdbcTemplate;}@Transactionalpublic void restoreDatabase(String sqlPackagePath) {try {// 1. 获取数据库连接Connection connection = jdbcTemplate.getDataSource().getConnection();//2.拼接sql文件完整路径LocalDate now = LocalDate.now();int lastMonth = (now.getMonthValue()-1);lastMonth = (lastMonth == 0) ? 12 : lastMonth;String lastMonthDate = now.getYear()+"_"+lastMonth;//3.遍历数据表集合逐个执行SQL文件for (String dbName : db_names) {String sqlFileName = dbName+"_"+ lastMonthDate;String sqlFilePath = sqlPackagePath+sqlFileName+".sql";//执行SQL文件log.info("开始执行sql还原,Time:{}", LocalDateTime.now());ScriptUtils.executeSqlScript(connection, new SimpleResource(sqlFilePath));}// 4. 关闭连接connection.close();} catch (SQLException e) {log.info("Failed to restore database:{}",e);throw new RuntimeException("Failed to restore database", e);}}
}

2.创建用cmd命令执行sql的工具类

import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.LocalDateTime;@Slf4j
public class SqlImportUtil {public static boolean exportSql(String sqlFilePath) {log.info("开始执行:{}, Time:{}", sqlFilePath, LocalDateTime.now());String user = "demo";String password = "demo";String host = "localhost";String exportDatabaseName = "demo";// 使用拼接的方式来完成 DOS 命令String command = new String("mysql -h" + host + " -u" + user + " -p" + password + " " + exportDatabaseName + " < " + sqlFilePath);log.info("命令:{}", command);try {ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", command);processBuilder.redirectErrorStream(true);Process process = processBuilder.start();// 读取命令执行结果BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line;while ((line = reader.readLine()) != null) {// 输出命令执行结果,只在line不为空时打印if (!line.trim().isEmpty()) {log.info("cmd命令框数据:{}", line);}}// 等待命令执行完成int exitCode = process.waitFor();if (exitCode == 0) {log.info("执行结束:{}, Time:{}", sqlFilePath, LocalDateTime.now());return true;} else {log.error("执行失败,命令返回码:{}", exitCode);}} catch (IOException | InterruptedException e) {log.error("执行命令时发生异常", e);}return false;}
}
  • 用ProcessBuilder类来执行拼接出来的cmd命令
  • 用Process类来阻塞从而任务完成时输出任务结果日志

3. 创建数据库切换配置并重启的service类

import com.database_reduction.event.RestartDatabaseEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
/*** 切换数据库配置并重启mysql的servic类*/
@Service
@Slf4j
public class DatabaseSwitchService implements ApplicationEventPublisherAware {@Value("${mysql.config-file-path}")private String mysqlConfigFilePath;@Value("${mysql.new-config-file-path}")private String newMysqlConfigFilePath;@Value("${mysql.backup-config-file-path}")private String backupConfigFilePath;private ApplicationEventPublisher eventPublisher;public void restoreAndRestartDatabase() {// 还原数据库配置文件restoreConfigFile();log.info("还原数据库配置文件");// 重启 MySQL 服务log.info("重启 MySQL 服务");restartMysqlService();}private void restoreConfigFile() {try {// 使用备份的配置文件还原当前配置Files.copy(new File(backupConfigFilePath).toPath(), new File(mysqlConfigFilePath).toPath(), StandardCopyOption.REPLACE_EXISTING);} catch (IOException e) {e.printStackTrace();}}public void switchAndRestartDatabase() {// 备份当前配置文件backupConfigFile();log.info("备份当前配置文件");// 切换到新的配置文件switchConfigFile();log.info("切换到新的配置文件");// 重启 MySQL 服务restartMysqlService();log.info("重启 MySQL 服务");}private void backupConfigFile() {try {Path source = new File(mysqlConfigFilePath).toPath();Path backup = new File(mysqlConfigFilePath + ".backup").toPath();Files.copy(source, backup, StandardCopyOption.REPLACE_EXISTING);} catch (IOException e) {e.printStackTrace();}}private void switchConfigFile() {try {Files.copy(new File(newMysqlConfigFilePath).toPath(), new File(mysqlConfigFilePath).toPath(), StandardCopyOption.REPLACE_EXISTING);} catch (IOException e) {e.printStackTrace();}}private void restartMysqlService() {// 发布事件,通知监听者重启 MySQLeventPublisher.publishEvent(new RestartDatabaseEvent(this));}@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.eventPublisher = applicationEventPublisher;}
}
  • 实现ApplicationEventPublisherAware接口从而注入事件发布器,能够发布自定义的重启数据库事件来通知观察者做出相应操作(观察者模式)
  • 除了为了解耦、异步执行之外,发送重启数据库的事件也是为了保证切换配置之后才重启

3.1 配置mysql配置文件地址

mysql:config-file-path: D:\install\MySQL\MySQL Server 8.0\my.ininew-config-file-path: D:\install\MySQL\my.inibackup-config-file-path: D:\install\MySQL\MySQL Server 8.0\my.ini.backup
  •  config-file-path为mysql默认配置文件,new-config-file-path是要替换的新配置,backup-config-file-path为mysql默认配置文件的备份

3.1 创建新的mysql配置文件

        复制一个默认mysql配置文件并修改其对应参数

[mysqld]
skip-log-bin
#log-bin="USER-20220912IO-bin"
innodb_buffer_pool_size=2G
innodb_log_file_size=2G
innodb_log_buffer_size=8M
innodb_flush_log_at_trx_commit=2
  •  skip-log-bin        还原过程不生成日志文件,
    innodb_buffer_pool_size        写缓冲池内存
    innodb_log_file_size        日志大小             
  • 默认配置文件中若没有的字段可以不改

4. 创建事件类

import org.springframework.context.ApplicationEvent;public class RestartDatabaseEvent extends ApplicationEvent {public RestartDatabaseEvent(Object source) {super(source);}
}

5.创建观察者

import com.database_reduction.event.RestartDatabaseEvent;
import org.springframework.context.ApplicationListener;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;public class DatabaseRestartListener implements ApplicationListener<RestartDatabaseEvent> {@Overridepublic void onApplicationEvent(RestartDatabaseEvent event) {// 实现重启 MySQL 服务的逻辑try {ProcessBuilder processBuilder = new ProcessBuilder("net", "stop", "MySQL80");processBuilder.redirectErrorStream(true);Process process = processBuilder.start();// 读取命令执行结果BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line;while ((line = reader.readLine()) != null) {System.out.println("命令执行结果:" + line);}// 等待命令执行完成int exitCode = process.waitFor();if (exitCode == 0) {System.out.println("MySQL服务停止成功");// 启动MySQL服务startMysqlService();} else {System.err.println("MySQL服务停止失败,错误码:" + exitCode);}} catch (IOException | InterruptedException e) {e.printStackTrace();}}private void startMysqlService() {try {ProcessBuilder processBuilder = new ProcessBuilder("net", "start", "MySQL80");processBuilder.redirectErrorStream(true);Process process = processBuilder.start();// 读取命令执行结果BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line;while ((line = reader.readLine()) != null) {System.out.println("命令执行结果:" + line);}// 等待命令执行完成int exitCode = process.waitFor();if (exitCode == 0) {System.out.println("MySQL服务启动成功");} else {System.err.println("MySQL服务启动失败,错误码:" + exitCode);}} catch (IOException | InterruptedException e) {e.printStackTrace();}}
}
  • 同样用的ProcessBuilfer执行cmd命令
  • 注意这里的MySQL80,是mysql的服务名,不知道自己mysql服务名的可以win+r输入services.msc 查看

 6.创建定时任务类

import com.database_reduction.service.DataBaseRestoreByCmdService;
import com.database_reduction.service.DatabaseSwitchService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import java.time.LocalDate;
import java.time.LocalDateTime;/*** 还原数据库定时类*/
@Component
@Slf4j
public class ReductionTask {//sql文件所在路径private String sqlPackagePath  = "E:/project/DataBaseBackup/sql/";//使用 Spring 的 @Value 注解注入属性值@Value("${LOG_MONTH}")private String logMonth;@Autowiredprivate DatabaseSwitchService databaseSwitchService;@Autowiredprivate DataBaseRestoreByCmdService dataBaseRestoreByCmdService;/*** 每月的2号0点还原数据库*/@Scheduled(cron = "0 0 0 2 * ?")public void executeTask(){// 切换数据库配置并重启 MySQLdatabaseSwitchService.switchAndRestartDatabase();log.info("开始还原数据库:{}", LocalDateTime.now());dataBaseRestoreByCmdService.initiateRestore(sqlPackagePath);//还原数据库配置并重启databaseSwitchService.restoreAndRestartDatabase();}@Scheduled(cron = "0 0 0 1 * ?")  // 每月1号执行一次public void updateLogMonth() {// 生成新的月份值,例如 2023_12String newMonth = generateNewMonth();log.info("更新log文件月份:{}", newMonth);// 更新 LOG_MONTH 的值logMonth = newMonth;}private String generateNewMonth() {LocalDate now = LocalDate.now();int month = (now.getMonthValue());String nowMonth = now.getYear()+"_"+month;return nowMonth;}
}

6.1 主启动类上添加注解开启定时任务

@SpringBootApplication
@EnableScheduling
public class DatabaseBackupApplication {public static void main(String[] args) {SpringApplication.run(DatabaseBackupApplication.class, args);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/201849.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dp-基础版动态规划(动态规划每日一题计划)10/50

最小路径和 class Solution {public static int minPathSum(int[][] grid) {int dp[][]new int[grid.length][grid[0].length];dp[0][0]grid[0][0];for(int i1;i<grid[0].length;i){dp[0][i]grid[0][i]dp[0][i-1];}for(int i1;i<grid.length;i){dp[i][0]grid[i][0]dp[i-…

江苏开放大学形成性考核 平时作业参考试题

试卷代号&#xff1a;1317 社会工作行政&#xff08;本&#xff09; 参考试题 一、单项选择题&#xff08;每题只有一个正确答案&#xff0c;请将正确答案的字母填写在括号内。每题1分&#xff0c;共8分&#xff09; 1.面对新冠肺炎疫情带来的不确定性和挑战&#xff0c;某社…

linux中slab与slub的实现区别

整体上slub的实现比slab更清爽&#xff0c;连带着方便测试和debug slab 与 slub 实现对比 SLABSLUBcpu_cache上缓存的对象以slab中的某个对象为单位&#xff0c;批发到cpu_cache上以整个slab为单位装入cpu_cache上cpu_cache结构struct array_cache struct kmem_cache_cpu 是否支…

C++红黑树封装set和map(很详细)

前言 在前面&#xff0c;我们学习了红黑树。&#xff08;没学过红黑树直接看会很吃力&#xff09;set和map的底层就是红黑树&#xff0c;现在我们要用这棵树来封装STL里面的容器&#xff1a;set和map。 下面是之前讲过的红黑树&#xff0c;他只是普通的“Key”模型,适合封装set…

BabyLIN产品如何轻松搞定K线协议实现?

来源&#xff1a;虹科汽车电子 虹科技术丨BabyLIN产品如何轻松搞定K线协议实现&#xff1f; 原文链接&#xff1a;https://mp.weixin.qq.com/s/LR7VHFQajjyw6s6bVDJmsg 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; 导读 为了实现K线通信&#xff0c;SDF-V3在协议…

redis性能测试

redis性能测试 redis提供了一个性能测试工具redis-benchmark&#xff0c;可以使用redis-benchmark命令来了解redis的性能 redis-benchmark -q -c 50----------------------q 表示简化输出结果-c 50 表示有五十个客户端-n <requests> 客户端请求总量 运行结果展示了一些常…

类的加载器

文章目录 1. 类加载器的分类2. 双亲委派模型3. 沙箱安全机制 1. 类加载器的分类 口述&#xff1a;启动类加载器加载jvm需要的核心的类库&#xff0c;扩展类加载器加载外部的jar包&#xff0c;应用程序加载器加载我们自己定义的类。 2. 双亲委派模型 规定了类加载的顺序是&a…

FAQ:Reference篇

文章目录 What is a reference?What happens if you assign to a reference?What happens if you return a reference?What does object.method1().method2() mean?How can you reseat a reference to make it refer to a different object?Why does C have both pointers…

m1源码编译xgboost的动态链接库dylib

1、下载源码 git clone --recursive https://github.com/dmlc/xgboost cd xgboost拉取源码时候&#xff0c;一定要加"--recursive"这个命令。把它的字模块也要拉取下来&#xff0c;才能编译成功 2、安装c依赖 必要的依赖项(不然后续编译时报错)&#xff0c;包括CM…

用java比较两个二叉搜索树是否等价

一. 定义树的的节点 ​ 不同二叉树的叶节点上可以保存相同的值序列。例如&#xff0c;以下两个二叉树都保存了序列 1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13。 package com.wedoo.coderyeah.module.iot.algorithm;import lombok.…

【CentOS】配置 Apache 服务

yum install httpd -y# 查看是否安装成功 httpd -v # 出现版本号表示成功# 启动服务 systemctl start httpd# 查看状态 systemctl status httpd # running 即可成功 ● httpd.service - The Apache HTTP ServerLoaded: loaded (/usr/lib/systemd/system/httpd.service; disable…

Redlock算法实现Redis分布式锁

Redlock算法实现Redis分布式锁 为什么基于故障转移的实现还不够 使用 Redis 锁定资源的最简单方法是在实例中创建密钥。密钥通常是在有限的生存时间内创建的&#xff0c;使用 Redis 过期功能&#xff0c;以便最终它被释放&#xff08;我们列表中的属性 2&#xff09;。当客户…

每日一题 2646. 最小化旅行的价格总和(困难,树)

分解为两个子问题&#xff0c;树中节点到节点的路径问题&#xff0c;价格减半树的最小值问题由于它是无向的树&#xff0c;所以对于每一次旅行&#xff0c;以 start 为根&#xff0c;通过dfs寻找 end 就可以很简单地找到需要的路径且它是唯一的&#xff0c;这里我们统计每经过一…

Splashtop 荣获 SDC“年度安全供应商”奖

2023年12月5日 荷兰阿姆斯特丹 Splashtop 是随处办公环境改革的先驱&#xff0c;在伦敦举办的第14届 SDC 颁奖典礼上荣获“年度安全供应商”奖&#xff0c;我们对此感到十分自豪。荣获这一知名奖项凸显了 Splashtop 致力于通过企业级加密和基于权限的访问保护不同规模组织的决…

准确!!!在 CentOS 8 上配置 PostgreSQL 14 的主从复制

在 CentOS 8 上配置 PostgreSQL 14 的主从复制&#xff0c;并设置 WAL 归档到特定路径 /home/postgres/archive 的步骤如下&#xff1a; 主服务器配置&#xff08;主机&#xff09; 配置 PostgreSQL&#xff1a; 编辑 postgresql.conf 文件&#xff1a; vim /data/postgres/p…

iis--IIS8中安装和使用URL重写工具(URL Rewrite)的方法

URL重写-下载 IIS8中安装和使用URL重写工具(URL Rewrite)的方法 IIS版本号可以被识别&#xff0c;修复图解&#xff0c;亲测有效

我的NPI项目之Android 安全系列 -- 天地混沌初开

最近在因为有新项目启动&#xff0c;目前处在kickoff之后research阶段&#xff0c;预计在1st March能拿到到Pre-EVT&#xff1b; 在此之前最主要的就是需求分析/可行性分析/风险评估。 而对于软件来说&#xff0c;作为传说中的software project leader&#xff0c;要做的最重要…

运维之远程桌面连接失败问题排查

背景&#xff1a;同一局域网&#xff0c;可以ping通但是远程连接不上&#xff0c;排查一下问题。 1、被远程计算机是否允许远程连接 2、被远程计算机防火墙是否允许 3、被远程计算机远程桌面服务是否正常 4、查看用户权限

mfc项目设置软件版本

//上面设置的版本通过下面的代码可以获取到 TSTRING CVersion::GetSoftVersion() {TSTRING strVer _T("");TCHAR szPath[MAX_PATH] _T("");memset(szPath, 0, sizeof(szPath));::GetModuleFileName(NULL, szPath, sizeof(szPath));//得到本程序的目录UIN…

Ubuntu22.04 安装nvida-docker2和改路径

在 Ubuntu 22.04 上安装 nvidia-docker2 可以通过一系列步骤完成。nvidia-docker2 是一个用于运行 Docker 容器的工具&#xff0c;它使容器能够访问宿主机的 NVIDIA GPU。以下是安装过程&#xff1a; 1. 安装 Docker 首先&#xff0c;确保已经安装了 Docker。如果没有安装 Do…