目录
一、环境信息
|------|--------------------------------------------------------------------------------------------------------------------|
| 名称 | 值 |
| 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)