- 语言:Python 3
- 数据库:PostgreSQL 12
- 系统:Windows 10
相关目录参数
将可能需要更改的目录参数等放在起始部分,根据实际情况进行修改即可。
import psycopg2
import datetime
import os
import shutil
# 可更改的参数
#######################################################################################################################
# data目录
src = 'D:\\Program Files\\PostgreSQL\\12\\data'
# 备份存放目录
dst = 'D:\\Program Files\\PostgreSQL\\backup'
# 归档存放目录
archive_dir = 'D:\\Program Files\\PostgreSQL\\backup\\archivelog'
# pg_archivecleanup.exe所在目录
archivecleancommand_dir = "D:\\" + '"' + "Program Files" + '"' + "\\PostgreSQL\\12\\bin\\pg_archivecleanup.exe"
# log_file文件的路径
log_file = dst + '\\' + 'log.history'
# 备份保留个数
num = 3
#######################################################################################################################
数据库操作类
class BackupDb:
# 初始化
def __init__(self, database, user, pwd, host, port):
self.database = database
self.user = user
self.pwd = pwd
self.host = host
self.port = port
self.conn = None
self.cur = None
连接及关闭数据库连接
# 连接数据库
def connect_db(self):
self.conn = psycopg2.connect(database=self.database, user=self.user, password=self.pwd, host=self.host,
port=self.port)
if self.conn:
self.cur = self.conn.cursor()
# 关闭数据库
def close_db(self):
if self.conn:
if self.cur:
self.cur.close()
self.conn.close()
备份准备
# 备份前的准备工作
def prepare_backup(self):
# 备份目录编号,初始为1,递增
nums = 1
# 如果log_file存在,得到行数,获取最后一行中的编号信息,表示备份编号已经到达该数值。如果不存在就创建log_file文件
if os.path.exists(log_file):
with open(log_file, 'r') as lf:
log_lines = lf.readlines()
length = len(log_lines)
# 最后一行中的编号信息
nums = int(log_lines[length - 1].split(',')[0])
else:
self.create_log()
# 搜寻备份路径,找到最后一次备份的目录编号,然后编号+1,准备进行备份
while os.path.exists(dst + '\\' + str(nums)):
nums += 1
return nums
备份数据库
# 备份data目录及其他目录中的表空间
def database_bakcup(self, b_num):
# 如果已经开始pg_start_backup,会生成backup_label,此时需要pg_stop_backup
if os.path.exists(src + '\\backup_label'):
self.cur.execute("SELECT pg_stop_backup()")
else:
# 以日期为备份标签
backup_label = day_info
self.cur.execute("SELECT pg_start_backup('" + backup_label + "', true)")
# 将data的整个文件目录复制到备份路径下
shutil.copytree(src, dst + '\\' + str(b_num))
# 备份位于其他路径的表空间
self.cur.execute("select pg_catalog.pg_tablespace_location(oid) FROM pg_catalog.pg_tablespace")
tbs = self.cur.fetchall()
# 存在其他路径的表空间
if len(tbs) > 2:
# 对这些表空间依次进行备份
for tb in range(2, len(tbs)):
# 表空间路径
tb_src = tbs[tb][0]
# 表空间备份路径
tb_dst = dst + '\\' + str(b_num) + '\\' + tbs[tb][0].replace(':', '').replace('\\\\', '')
# 将表空间的整个文件目录复制到备份路径下
shutil.copytree(tb_src, tb_dst)
self.cur.execute("SELECT pg_stop_backup()")
备份删除
# 删除旧的备份
def delete_old_backup(self):
# 读取log_file文件的行数,即备份脚本运行次数,但序号可能有错,所以取最后一行中的序号,以此为最后一次的备份号
with open(log_file, 'r') as lf:
lines = lf.readlines()
line_numbers = len(lines)
# 提取最后一行信息中的序号
target_index = int(lines[line_numbers - 1].split(',')[0])
# 只保留num次备份,删除较早的备份
for n in range(1, target_index - num + 1):
dst_dir = dst + '\\' + str(n)
if os.path.exists(dst_dir):
try:
# 删除整个路径
shutil.rmtree(dst_dir)
except Exception as err:
print(err)
# 清理归档
self.archive_clean()
清理归档
# 清理归档文件
@ staticmethod
def archive_clean():
# 读取log_file文件的行数,即备份脚本运行次数
with open(log_file, 'r') as lf:
lines = lf.readlines()
line_number = len(lines)
# 找到行号为[行数-备份保留个数(num)+ 1]的行,读取其WAL写入位置
for ele in range(1, line_number + 1):
if ele == line_number - num:
target = lines[ele].split(',')[1]
# pg_archivecleanup命令语句
clean_command = archivecleancommand_dir + ' "' + archive_dir + '" ' + target
# 执行pg_archivecleanup命令
os.system(clean_command)
日志文件相关
# 首次运行脚本时,创建log_file文件
def create_log(self):
# 获得当前预写式日志(WAL)写入位置
self.cur.execute("select pg_walfile_name(pg_current_wal_lsn())")
xlog_path = self.cur.fetchall()
# 将信息写入log_file文件
with open(log_file, 'w') as lf:
lf.write(str(1) + ',' + xlog_path[0][0] + ',' + day_info)
# 每行格式类似于 1,000000010000000000000031,20181122111930
# 已经有log_file文件之后,每次运行脚本将依次向下添加信息
def modify_log(self, log_num):
# 获得当前预写式日志(WAL)写入位置
self.cur.execute("select pg_walfile_name(pg_current_wal_lsn())")
xlog_path = self.cur.fetchall()
# 将信息追加到log_file文件中
with open(log_file, 'a') as lf:
lf.write('\n' + str(log_num) + ',' + xlog_path[0][0] + ',' + day_info)
主程序
if __name__ == '__main__':
# 创建postgresql数据库对象
db = BackupDb(database='postgres', user='postgres', pwd='123456', host='127.0.0.1', port='5432')
try:
# 连接数据库
db.connect_db()
# 获取当前时间并对时间进行格式化
day_info = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
# 获取备份编号
num_res = db.prepare_backup()
# 第一次运行脚本,直接执行备份
if num_res == 1:
db.database_bakcup(num_res)
# 如果备份编号小于等于备份保留个数
elif num_res <= num:
# 执行备份
db.database_bakcup(num_res)
# 修改log_file,将信息追加到log_file文件中
db.modify_log(num_res)
# 如果备份编号大于备份保留个数,需要删除较早的备份
else:
# 执行备份
db.database_bakcup(num_res)
# 修改log_file,将信息追加到log_file文件中
db.modify_log(num_res)
# 删除较早的备份
db.delete_old_backup()
# 关闭postgresql数据库连接
db.close_db()
except Exception as e:
print(e)
总结
整个脚本实现的是数据库的备份操作,并设置了最大保留个数。当备份数超过设定的阈值时,会将较早的备份进行删除。