达梦数据库-学习-43-定时备份模式和删除备份(Python+Crontab)

目录

一、环境信息

二、说点什么

三、前提

四、实验

1、创建目录

2、创建PYTHON脚本

(1)源码

(2)参数

3、测试数据

4、配置定时任务

5、查看定时任务

6、查看脚本日志

7、查看定时调度情况


一、环境信息

|------|--------------------------------------------------------------------------------------------------------------------|
| 名称 | 值 |
| CPU | 12th Gen Intel(R) Core(TM) i7-12700H |
| 操作系统 | CentOS Linux release 7.9.2009 (Core) |
| 内存 | 7G |
| 逻辑核数 | 8 |
| DM版本 | DM Database Server 64 V8 DB Version: 0x7000d 03134284368-20250917-293539-20149 Msg Version: 44 Gsu level(5) cnt: 0 |

二、说点什么

最近客户有需求数据库定时备份,但只备份需要的几个模式,达梦内置的作业功能先只支持全库物理备份,不满足客户需求,所以写了个Py脚本,大家有相同需求的话,可以试试看。

三、前提

|----|----------------------------------------|
| 编号 | 描述 |
| 1 | 脚本需和达梦数据库放在一台服务器上,多节点放其中一个节点即可。 |
| 2 | 脚本的操作系统用户组需要和达梦数据库相同,dmdba用户dinstall组。 |
| 3 | 达梦数据库服务正常。 |

四、实验

1、创建目录

bash 复制代码
[dmdba@rhel709 ~]$ mkdir -p /home/dmdba/DmPy/Dmp
[dmdba@rhel709 ~]$ mkdir -p /home/dmdba/DmPy/Log

2、创建PYTHON脚本

创建DmExp.py放到目录DmPy下。

(1)源码

用的都是python基础库,所以理论上支持所有平台,不需要安装额外库。

python 复制代码
#encoding:utf-8

import logging
import inspect
import os
import sys
import subprocess
import datetime
from datetime import datetime

SCH_SET                 = r"SUN,MOON"                         # 建议修改,导出模式集合,用逗号隔开。
DB_USER                 = r"SYSDBA"                           # 建议修改,连接的数据库用户。
DB_PWD                  = r"Dameng@123"                       # 建议修改,连接的数据库用户的密码。
DM_BIN_PATH             = r"/dm/dmdbms/bin"                   # 建议修改,达梦安装的bin目录。
DMP_DIR                 = r"/home/dmdba/DmPy/Dmp"             # 建议修改,导出dmp文件和日志路径。
LOG_WRITE_PATH          = r"/home/dmdba/DmPy/Log/PyDm.Log"    # 建议修改,程序日志打印位置。
DM_DMP_RETENTION_CNT    = 5                                   # 按需求修改,导出dmp文件保留个数。
DM_LOG_RETENTION_CNT    = DM_DMP_RETENTION_CNT                # 按需求修改,导出dmp日志文件保留个数。

#以下变量不建议改动

LOG_FLAG_FILE           = 0                                   # 日志输出文件
LOG_FLAG_SCREEN         = 1                                   # 日志输出屏幕
LOG_FLAG_ALL            = 2                                   # 日志输出文件、屏幕
LOG_LV                  = logging.DEBUG
LOG_FLAG                = LOG_FLAG_ALL

DM_DMP_NAME_TAIL        = r".dmp"
DM_LOG_NAME_TAIL        = r".log"

SUCCESS_FLAG            = 0
FAIL_FLAG               = -1
NORMAL_FLAG             = 1                                   # 正常的标准,例如队列满了,栈满了,是正常的,算是成功和失败的中间结果。
EXCEPTION_EXIT_FLAG     = -2

def FuncNameGet():
    return inspect.currentframe().f_back.f_back.f_code.co_name

class Log:
    LogFilePath   = None
    LogLv         = None
    LogFlag       = None
    __Logger      = None
    __LogFormat   = None
    __LogHandle   = None
    __LogConsole  = None

    def __init__(self, LogFilePath, LogLv, LogFlag):
        self.LogFilePath = LogFilePath
        self.LogLv       = LogLv
        self.LogFlag     = LogFlag

        # 创建记录器
        self.__Logger    = logging.getLogger(__name__)

        # 设置日志级别
        self.__Logger.setLevel(self.LogLv)

        # 格式器
        self.__LogFormat  = logging.Formatter('[%(asctime)s]-P[%(process)d]-T[%(thread)d]-[%(levelname)s]-%(message)s')

        # 判断输出位置
        if self.LogFlag == LOG_FLAG_FILE or self.LogFlag == LOG_FLAG_ALL:
            # 日志写入到文件
            self.__LogHandle = logging.FileHandler(self.LogFilePath)
            self.__LogHandle.setLevel(self.LogLv)
            self.__LogHandle.setFormatter(self.__LogFormat)
            self.__Logger.addHandler(self.__LogHandle)
            
        if self.LogFlag == LOG_FLAG_SCREEN or self.LogFlag == LOG_FLAG_ALL:
            # 控制台输出
            self.__LogConsole = logging.StreamHandler()
            self.__LogConsole.setLevel(self.LogLv)
            self.__LogConsole.setFormatter(self.__LogFormat)
            self.__Logger.addHandler(self.__LogConsole)

    def LogDebug(self, Msg, FuncName=None):
        if FuncName is None:
            FuncName = FuncNameGet()
        self.__Logger.debug("%-18s : OK, %s." % (FuncName, Msg))

    def LogInfo(self, Msg, FuncName=None):
        if FuncName is None:
            FuncName = FuncNameGet()
        self.__Logger.info("%-18s : OK, %s." % (FuncName, Msg))

    def LogWarn(self, Msg, FuncName=None):
        if FuncName is None:
            FuncName = FuncNameGet()
        self.__Logger.warning("%-18s : Normal, %s." % (FuncName, Msg))

    def LogError(self, Msg, FuncName=None):
        if FuncName is None:
            FuncName = FuncNameGet()
        self.__Logger.error("%-18s : Fail, %s." % (FuncName, Msg))

MyLog = Log(LOG_WRITE_PATH,LOG_LV,LOG_FLAG)

def OsCmdLocal(OsCmd, Input = ""):
    Process = subprocess.Popen(OsCmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)

    # args                  :要执行的命令,可以是字符串或列表。如果是字符串,需要设置shell = True;如果是列表,则不需要。
    # shell                 :是否通过shell,执行命令,默认为False。
    # universal_newlines参数 :果把universal_newlines设置成True,则子进程的stdout和stderr被视为文本对象,并且不管是 * nix的行结束符('/n'),还是老mac格式的行结束符('/r' ),还是windows格式的行结束符('/r/n' )都将被视为'/n' 。

    Out, Err = Process.communicate(Input)

    # 与子进程进行交互。向stdin发送数据,或从stdout和stderr中读取数据。可选参数input指定发送到子进程的参数。
    # Communicate()返回一个元组:(stdoutdata,stderrdata)。
    # 注意:如果希望通过进程的stdin向其发送数据,在创建Popen对象的时候,参数stdin必须被设置为PIPE。
    # 同样,如果希望从stdout和stderr获取数据,必须将stdout和stderr设置为PIPE。

    if Process.returncode == 0:
        MyLog.LogDebug("Cmd = \"%s\", Input = \"%s\""%(OsCmd,Input))
        return SUCCESS_FLAG
    else:
        if Err == '':
            MyLog.LogWarn("Cmd = \"%s\", Input = \"%s\"" % (OsCmd,Input))
            return NORMAL_FLAG
        else:
            MyLog.LogError("Cmd = \"%s\", Input = \"%s\", Err = \"%s\"" % (OsCmd,Input,Err))
            return FAIL_FLAG

def DmExpSchData(DmBinPath,SchSet,DbUser,DbPwd,DmpDir):
    DATE_NOW        = datetime.now().strftime("%Y-%m-%d_%H_%M_%S_%f")
    PWD             = "'\"%s\"'" % (DbPwd)
    CMD             = r'%s/dexp USERID=%s/%s FILE=DmExpSchData_%s%s LOG=DmExpSchData_%s%s DIRECTORY=%s SCHEMAS=%s PARALLEL=15 COMPRESS=Y COMPRESS_LEVEL=5' % \
                      (DmBinPath,DbUser,PWD,DATE_NOW,DM_DMP_NAME_TAIL,DATE_NOW,DM_LOG_NAME_TAIL,DmpDir,SchSet)

    State           = OsCmdLocal(CMD)

    if State == SUCCESS_FLAG:
        MyLog.LogDebug("SchSet = \"%s\", DbUser = \"%s\", DbPwd = \"%s\", DmpDir = \"%s\""%(SchSet,DbUser,DbPwd,DmpDir))
    elif State == NORMAL_FLAG:
        MyLog.LogWarn("SchSet = \"%s\", DbUser = \"%s\", DbPwd = \"%s\", DmpDir = \"%s\""%(SchSet,DbUser,DbPwd,DmpDir))
    else:
        MyLog.LogError("SchSet = \"%s\", DbUser = \"%s\", DbPwd = \"%s\", DmpDir = \"%s\""%(SchSet,DbUser,DbPwd,DmpDir))
        
    return State

def OsFileNameGet(Dir,FileSet,LogSet):
    for File in os.listdir(Dir):
        if File.find(DM_DMP_NAME_TAIL) != -1:
            FileSet.append(Dir + '/' +File)
        elif File.find(DM_LOG_NAME_TAIL) != -1:
            LogSet.append(Dir + '/' +File)

    FileSet.sort()
    LogSet.sort()

    MyLog.LogDebug("Dir = \"%s\"" % (Dir))
    return SUCCESS_FLAG

def OsDelDmp(FileSet,RetentionCnt):
    Cnt    = len(FileSet)
    DelSet = []

    if Cnt <= RetentionCnt:
        MyLog.LogDebug("TotalCnt = %u, DelCnt = 0, do not delete" % (Cnt))
        return NORMAL_FLAG

    try:

        for i in range(Cnt - RetentionCnt):
            DelSet.append(FileSet[i])
            os.remove(FileSet[i])

        MyLog.LogDebug("TotalCnt = %u, DelCnt = %u, DelSet = %s" % (Cnt,len(DelSet),DelSet))
        return SUCCESS_FLAG

    except OSError as ErrDesc:
        MyLog.LogError("DelSet = %s, ErrDesc = %s"%(DelSet,ErrDesc))
        return FAIL_FLAG

if __name__ == "__main__":
    DmpSet          = []
    LogSet          = []

    if DmExpSchData(DM_BIN_PATH,SCH_SET,DB_USER,DB_PWD,DMP_DIR) != SUCCESS_FLAG:
        sys.exit(FAIL_FLAG)

    OsFileNameGet(DMP_DIR,DmpSet,LogSet)
    
    if OsDelDmp(DmpSet,DM_DMP_RETENTION_CNT) != SUCCESS_FLAG:
        sys.exit(FAIL_FLAG)

    if OsDelDmp(LogSet,DM_LOG_RETENTION_CNT) != SUCCESS_FLAG:
        sys.exit(FAIL_FLAG)

    sys.exit(SUCCESS_FLAG)

(2)参数

大家按照自己的实际情况修改。

|----------------------|----------------------|
| 参数名 | 描述 |
| SCH_SET | 建议修改,导出模式集合,用逗号隔开。 |
| DB_USER | 建议修改,连接的数据库用户。 |
| DB_PWD | 建议修改,连接的数据库用户的密码。 |
| DM_BIN_PATH | 建议修改,达梦安装的bin目录。 |
| DMP_DIR | 建议修改,导出dmp文件和日志路径。 |
| LOG_WRITE_PATH | 建议修改,程序日志打印位置。 |
| DM_DMP_RETENTION_CNT | 按需求修改,导出dmp文件保留个数。 |
| DM_LOG_RETENTION_CNT | 按需求修改,导出dmp日志文件保留个数。 |

3、测试数据

bash 复制代码
CREATE USER "SUN" IDENTIFIED BY "qwer1234S@";
GRANT DBA TO SUN;
CREATE USER "MOON" IDENTIFIED BY "qwer1234S@";
GRANT DBA TO MOON;

4、配置定时任务

dmdba用户操作。

bash 复制代码
crontab -e

添加如下内容,每分钟执行一次PY脚本。

bash 复制代码
* * * * * /bin/python /home/dmdba/DmPy/DmExp.py

实际使用可以写如下内容,每周六1点执行一次PY脚本。

bash 复制代码
0 1 * * 6 /bin/python /home/dmdba/DmPy/DmExp.py

5、查看定时任务

bash 复制代码
[dmdba@rhel709 DmPy]$ crontab -l
* * * * * /bin/python /home/dmdba/DmPy/DmExp.py

6、查看脚本日志

bash 复制代码
[dmdba@rhel709 DmPy]$ tail -100f Log/PyDm.Log 
[2025-11-27 15:08:01,446]-P[7846]-T[140452363245376]-[DEBUG]-OsFileNameGet      : OK, Dir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:08:01,446]-P[7846]-T[140452363245376]-[DEBUG]-OsDelDmp           : OK, TotalCnt = 3, DelCnt = 0, do not delete.
[2025-11-27 15:09:01,825]-P[7925]-T[139940390467392]-[DEBUG]-OsCmdLocal         : OK, Cmd = "/dm/dmdbms/bin/dexp USERID=SYSDBA/'"Dameng@123"' FILE=DmExpSchData_2025-11-27_15_09_01_695523.dmp LOG=DmExpSchData_2025-11-27_15_09_01_695523.log DIRECTORY=/home/dmdba/DmPy/Dmp SCHEMAS=SUN,MOON PARALLEL=15 COMPRESS=Y COMPRESS_LEVEL=5", Input = "".
[2025-11-27 15:09:01,826]-P[7925]-T[139940390467392]-[DEBUG]-DmExpSchData       : OK, SchSet = "SUN,MOON", DbUser = "SYSDBA", DbPwd = "Dameng@123", DmpDir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:09:01,826]-P[7925]-T[139940390467392]-[DEBUG]-OsFileNameGet      : OK, Dir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:09:01,826]-P[7925]-T[139940390467392]-[DEBUG]-OsDelDmp           : OK, TotalCnt = 4, DelCnt = 0, do not delete.
[2025-11-27 15:10:02,029]-P[8129]-T[139708749952832]-[DEBUG]-OsCmdLocal         : OK, Cmd = "/dm/dmdbms/bin/dexp USERID=SYSDBA/'"Dameng@123"' FILE=DmExpSchData_2025-11-27_15_10_01_929033.dmp LOG=DmExpSchData_2025-11-27_15_10_01_929033.log DIRECTORY=/home/dmdba/DmPy/Dmp SCHEMAS=SUN,MOON PARALLEL=15 COMPRESS=Y COMPRESS_LEVEL=5", Input = "".
[2025-11-27 15:10:02,029]-P[8129]-T[139708749952832]-[DEBUG]-DmExpSchData       : OK, SchSet = "SUN,MOON", DbUser = "SYSDBA", DbPwd = "Dameng@123", DmpDir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:10:02,029]-P[8129]-T[139708749952832]-[DEBUG]-OsFileNameGet      : OK, Dir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:10:02,030]-P[8129]-T[139708749952832]-[DEBUG]-OsDelDmp           : OK, TotalCnt = 5, DelCnt = 0, do not delete.
[2025-11-27 15:11:01,213]-P[8274]-T[139750630733632]-[DEBUG]-OsCmdLocal         : OK, Cmd = "/dm/dmdbms/bin/dexp USERID=SYSDBA/'"Dameng@123"' FILE=DmExpSchData_2025-11-27_15_11_01_121691.dmp LOG=DmExpSchData_2025-11-27_15_11_01_121691.log DIRECTORY=/home/dmdba/DmPy/Dmp SCHEMAS=SUN,MOON PARALLEL=15 COMPRESS=Y COMPRESS_LEVEL=5", Input = "".
[2025-11-27 15:11:01,214]-P[8274]-T[139750630733632]-[DEBUG]-DmExpSchData       : OK, SchSet = "SUN,MOON", DbUser = "SYSDBA", DbPwd = "Dameng@123", DmpDir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:11:01,214]-P[8274]-T[139750630733632]-[DEBUG]-OsFileNameGet      : OK, Dir = "/home/dmdba/DmPy/Dmp".
[2025-11-27 15:11:01,216]-P[8274]-T[139750630733632]-[DEBUG]-OsDelDmp           : OK, TotalCnt = 6, DelCnt = 1, DelSet = ['/home/dmdba/DmPy/Dmp/DmExpSchData_2025-11-27_15_01_01_725828.dmp'].
[2025-11-27 15:11:01,216]-P[8274]-T[139750630733632]-[DEBUG]-OsDelDmp           : OK, TotalCnt = 6, DelCnt = 1, DelSet = ['/home/dmdba/DmPy/Dmp/DmExpSchData_2025-11-27_15_01_01_725828.log'].

7、查看定时调度情况

bash 复制代码
[root@rhel709:/home/dmdba/DmBak]# tail -f /var/log/cron
Nov 27 15:07:01 rhel709 CROND[7765]: (dmdba) CMD (/bin/python /home/dmdba/DmPy/DmExp.py)
Nov 27 15:08:01 rhel709 CROND[7846]: (dmdba) CMD (/bin/python /home/dmdba/DmPy/DmExp.py)
Nov 27 15:09:01 rhel709 CROND[7925]: (dmdba) CMD (/bin/python /home/dmdba/DmPy/DmExp.py)
相关推荐
Grassto2 小时前
9 Go Module 依赖图是如何构建的?源码解析
开发语言·后端·golang·go module
软件开发技术深度爱好者2 小时前
JavaScript的p5.js库使用详解(上)
开发语言·javascript
首席拯救HMI官2 小时前
【拯救HMI】HMI容错设计:如何减少操作失误并快速纠错?
大数据·运维·前端·javascript·网络·学习
独自破碎E2 小时前
包含min函数的栈
android·java·开发语言·leetcode
DemonAvenger2 小时前
Redis监控系统搭建:关键指标与预警机制实现
数据库·redis·性能优化
沛沛老爹2 小时前
基于Spring Retry实现的退避重试机制
java·开发语言·后端·spring·架构
_F_y2 小时前
数据库基础
数据库·adb
zgl_200537792 小时前
源代码:ZGLanguage 解析SQL数据血缘 之 显示 UNION SQL 结构图
大数据·数据库·数据仓库·sql·数据治理·sql解析·数据血缘
wregjru2 小时前
【C++】2.9异常处理
开发语言·c++·算法