提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、前提条件
- 1、Python驱动部署
- 2、达梦数据库安装
- 二、主函数
- 三、基础信息
- 四、数据库IP读取
- 五、日志文件生成
- 六 SQL解析
- 列表拆分
- 数据库访问
- 总结
前言
本期和大家一起学习如何基于Python3.X将SQL批量应用到多个达梦多服务器,很多同学在数据库维护过程中可能都会遇到这样的情况,一条DDL或者DML语句需要应用到多个数据库,几个数据库实例都能轻松搞定,但实例达到一定的体量靠人工处理耗费大量时间。
一、前提条件
1、Python驱动部署
Python访问达梦数据库参考
https://eco.dameng.com/docs/zh-cn/start/python-development.html
2、达梦数据库安装
达梦数据库安装请参考
https://blog.csdn.net/qq_39181479/article/details/124699286
二、主函数
# -*- coding: utf-8 -*-
# @File : runMainFun.py
# @Date : 2021-08-27
# @Author : Richard
import datetime
import os
import time
from RunSqlToDm.app.get_base_info import Read_ini
from RunSqlToDm.app.get_server_info import ExcelData
from RunSqlToDm.app.get_sql_stam import ResoluSql
from RunSqlToDm.app.db import DB
from RunSqlToDm.app.get_comp_info import GetCompName
from RunSqlToDm.app.set_log import write_log
from RunSqlToDm.app.run_sql_stam import run_sql_tra
from RunSqlToDm.explog.out_log import log_out
from RunSqlToDm.app.mk_log import mk_log_file
from RunSqlToDm.app.getGuid import getGuidByTenaID
from multiprocessing import Pool, Process, cpu_count
from RunSqlToDm.app.list_cut import CutList
"""
内部使用不写MD文件
使用方法
1、修改conf目录下base.ini,comp_info.ini,pwd.ini文件,文件里有说明如何填写
2、编辑serverInfo.xlsx 追加需要应用SQL的数据库节点,可加多个,目前不限制服务器个数
"""
# 主函数
def main():
# 数据库及文件目录信息
read_ini = Read_ini()
dir_data = read_ini.values_by_key(sec="dir")
db_data = read_ini.values_by_key(sec="database")
# 服务器IP地址文件
xls_dir = os.path.join(dir_data[0], "cnf\serverInfo.xlsx")
# SQL文件所在目录
sql_dir = os.path.join(dir_data[0], "sql")
# 单位字典文件所在目录
com_dir = os.path.join(dir_data[0], "cnf\comp_info.ini")
# pwd字典
pwd_file = os.path.join(dir_data[0], "cnf\pwd.ini")
# 获取服务器IP
get_excel_data = ExcelData(file=xls_dir, sh=db_data[3])
server_data = get_excel_data.readExcel()
# 核数
num_cores = cpu_count()
# 进程池
p = Pool(num_cores)
# 生成日志文件
mk_file_flag = mk_log_file(dir_data[0])
if int(mk_file_flag) == 1:
log_out(msg="日志文件已存在!")
else:
log_out(msg="日志文件创建成功")
# 获取SQL文件
resolusql = ResoluSql(file=sql_dir)
sql_file = resolusql.get_sql_file()
# 获取单位信息
# getCompName = GetCompName(file=com_dir)
# 开始处理SQL语句
for itmes in server_data:
ip = itmes[db_data[2]]
com_name = itmes[db_data[13]]
tena_id = itmes[db_data[9]]
system_ident = itmes[db_data[11]]
domain_name = itmes[db_data[14]]
pbx_code = itmes[db_data[12]]
pass_id = itmes[db_data[4]]
meting_api = itmes[db_data[5]]
id7 = itmes[db_data[6]]
id8 = itmes[db_data[7]]
# guid_id = itmes[db_data[8]]
log_out(msg="即将在[{0}]单位执行".format(com_name))
# list_subsets = []
# 遍历SQL文件
for sf in sql_file:
# 整个文件读
if int(dir_data[1]) == 0:
sqls = resolusql.read(sf)
guid = getGuidByTenaID(ip=ip, user=db_data[0], port=db_data[1], tena_id=tena_id)
db = DB(sql=sqls, ip=ip, user=db_data[0], port=db_data[1], comp_file=com_dir, pwd_file=pwd_file)
db.run_sql(guid, tena_id, system_ident, domain_name, pbx_code, pass_id, meting_api, id7, id8)
# for i in range(1):
# res = p.apply_async(func=db.run_sql, args=(
# guid, tena_id, system_ident, domain_name, pbx_code, pass_id, meting_api, id7, id8,))
# 行方式读
elif int(dir_data[1]) == 1:
sqls = resolusql.read_by_row(file=sf)
guid = getGuidByTenaID(ip=ip, user=db_data[0], port=db_data[1], tena_id=tena_id)
# 切割SQL列表,根据实际CPU核数切割
cutList = CutList(list_info=sqls, per_len=num_cores)
list_subsets = cutList.cut() # 当列表长度小于cpu核数时,被切割的小列表将会小于num_cores,
# 列表切割数量和CPU核数相等
if int(len(list_subsets)) == num_cores:
for i in range(num_cores):
db = DB(ip=ip, user=db_data[0], port=db_data[1], comp_file=com_dir, pwd_file=pwd_file)
res = p.apply_async(func=db.run_mul,
args=(
list_subsets[i], tena_id, com_name, domain_name, guid, system_ident,
pbx_code, pass_id, meting_api, id7, id8))
else: # 子列表不等于CPU核数
# print("子列表小于CPU核数!将按照子列表个数开辟进程")
for fi in range(len(list_subsets)):
db = DB(ip=ip, user=db_data[0], port=db_data[1], comp_file=com_dir, pwd_file=pwd_file)
res = p.apply_async(func=db.run_mul,
args=(
list_subsets[fi], tena_id, com_name, domain_name, guid, system_ident,
pbx_code, pass_id, meting_api, id7, id8))
# 按照;号获取SQL文件,只适用创建表语句
elif int(dir_data[1]) == 3:
guid = getGuidByTenaID(ip=ip, user=db_data[0], port=db_data[1], tena_id=tena_id)
sqls = resolusql.open_sql_file(sql_file=sf)
# 切割SQL列表,根据实际CPU核数切割
cutList = CutList(list_info=sqls[0], per_len=num_cores)
list_subsets = cutList.cut()
# 子列表和CPU核数相等的时候
if int(len(list_subsets)) == num_cores:
for i in range(num_cores):
db = DB(ip=ip, user=db_data[0], port=db_data[1], comp_file=com_dir, pwd_file=pwd_file)
res = p.apply_async(func=db.run_mul,
args=(list_subsets[i], tena_id, com_name, domain_name, guid,))
else: # 子列表小于CPU核数
# print("创建数据库对象子列表机器CPU核数不相等!")
for fi in range(len(list_subsets)):
db = DB(ip=ip, user=db_data[0], port=db_data[1], comp_file=com_dir, pwd_file=pwd_file)
res = p.apply_async(func=db.run_mul,
args=(list_subsets[fi], tena_id, com_name, domain_name, guid,))
# 作为一个事务执行
elif int(dir_data[1]) == 2:
sqls = resolusql.read_by_row(file=sf)
guid = getGuidByTenaID(ip=ip, user=db_data[0], port=db_data[1], tena_id=tena_id)
run_sql_tra(ip=ip, user=db_data[0], port=db_data[1], data=sqls, com_file=com_dir, tena_id=tena_id,
rowguid=guid, system_ident=system_ident, pwd_file=pwd_file)
p.close()
p.join()
if __name__ == '__main__':
main()
三、基础信息
# -*- coding: utf-8 -*-
# @File : get_base_info.py
# @Date : 2021-11-25
# @Author : Richard
import os
import time
import configparser
class Read_ini(object):
def __init__(self):
self.ini_file = "D:\PycharmProjects\sqlRun\RunSqlToDm\cnf\\base.ini"
self.sec = "dir"
def get_values(self):
"""
读取ini文件,通过file与sec读取
:return: 返回对应的sec的values值
"""
try:
data = []
cf = configparser.ConfigParser()
cf.read(self.ini_file)
options = cf.options(self.sec)
for i in options:
itmes = cf.get(self.sec, i)
data.append(itmes)
return data
except Exception as Err:
print("read config path_files error:{0}".format(Err))
def values_by_key(self, sec):
try:
data = []
cf = configparser.ConfigParser()
cf.read(self.ini_file)
options = cf.options(sec)
for i in options:
res = cf.get(sec, i)
data.append(res)
return data
except Exception as Err:
print("open file config error,file type is ini:{0}".format(Err))
四、数据库IP读取
# -*- coding: utf-8 -*-
# @File : get_server_info.py
# @Date : 2021-11-25
# @Author : Richard
import os
import time
import pandas
import xlrd
from xlrd import xldate_as_tuple
# 解析Excel 类
class ExcelData(object):
# 初始化方法
def __init__(self, file, sh):
self.file = file
self.sh = sh
self.data = xlrd.open_workbook(self.file)
self.table = self.data.sheet_by_name(self.sh)
self.keys = self.table.row_values(0)
self.rowNum = self.table.nrows
self.colNum = self.table.ncols
# 定义一个读取excel表的方法
def readExcel(self):
datas = []
for i in range(1, self.rowNum):
sheet_data = {}
for j in range(self.colNum):
c_type = self.table.cell(i, j).ctype
# 获取单元格数据
c_cell = self.table.cell_value(i, j)
if c_type == 2 and c_cell % 1 == 0: # 如果是整形
c_cell = int(c_cell)
elif c_type == 3:
# 转成datetime对象
date = pandas.datetime.datetime(*xldate_as_tuple(c_cell, 0))
c_cell = date.strftime('%Y/%d/%m %H:%M:%S')
elif c_type == 4:
c_cell = True if c_cell == 1 else False
sheet_data[self.keys[j]] = c_cell
datas.append(sheet_data)
return datas
# 处理Excel
def open_excel(self):
"""
:param file_path:
:return: Database IP address
"""
data = []
try:
book = xlrd.open_workbook(self.file)
res_obj = book.sheet_by_name("server_ip")
for i in range(1, res_obj.nrows):
data.append(res_obj.cell(i, 1))
return data
except Exception as Err:
print("open excel error is :{0}".format(Err))
五、日志文件生成
# -*- coding: utf-8 -*-
# @File : mk_log.py
# @Date : 2022-01-10
# @Author : Richard
import os
import time
import datetime
def mk_log_file(base_path):
status = 0
if base_path:
try:
tt_time = datetime.datetime.now()
file_name = os.path.join(base_path,"logs") + '\\' + str(tt_time)[0:10] + '.log'
# 判断文件是否存在
file_exis = os.path.exists(file_name)
if file_exis:
status = 1
else:
with open(file_name, 'w') as file:
file.close()
status = 2
except Exception as Err:
print("创建日志文件发生错误,错误详细信息:{0}".format(Err))
finally:
return status
六 SQL解析
# -*- coding: utf-8 -*-
# @File : get_sql_stam.py
# @Date : 2021-11-25
# @Author : Richard
import os
import re
import time
# 解析SQL文件
class ResoluSql(object):
def __init__(self, file):
self.filepath = file
#
def ergodic_sql(self):
"""
:param:sql_file_path
:return: sqls info list
"""
try:
with open(self.filepath) as f:
sqls = f.readlines(100000)
return sqls
except Exception as Err:
print("ergodic statement error is :{0}".format(Err))
# 解析SQL文件
def get_sql_file(self):
"""
功能:根据目录遍历SQL文件
:param:sql_file_path
:return: SQL file ,data type is list
"""
try:
for root, dirs, files in os.walk(self.filepath):
data = []
for file in files:
res = os.path.join(root, file).encode("utf8")
data.append(res)
return data
except Exception as Err:
print("ergodic statement error is :{0}".format(Err))
def open_sql_file(self, sql_file):
"""
功能:根据SQL文件解析出SQL语句
:param sql_file:
:return: sql statement list
"""
if sql_file:
try:
# with open(sql_file, encoding="GBK") as f:
with open(sql_file, encoding="utf-8") as f:
# res = f.readlines()
res = f.read()
if re.findall(r"begin", res) and re.findall(r"create", res):
resource = res.split("--")
codeline = 1
# 非代码块SQL
else:
resource = res.split(";")[:-1]
codeline = 0
return [resource, codeline]
except Exception as Err:
print("open sql file error,information more check[0]".format(Err))
else:
print("sql file not exists")
def read(self, sql_file):
"""
功能:根据SQL文件解析出SQL语句
:param sql_file:
:return: sql statement list
"""
if sql_file:
try:
# with open(sql_file, encoding="GBK") as f:
with open(sql_file, encoding="utf8") as f:
# res = f.readlines()
res = f.read()
if res:
return res
except Exception as Err:
print("open sql file error,information more check[{0}]".format(Err))
else:
print("sql file not exists")
def read_by_row(self, file):
"""
功能:根据SQL文件解析出SQL语句,以行的方式
:param sql_file:sql文件
:return: sql statement list
"""
if file:
try:
# with open(file, encoding="GBK") as f:
with open(file, encoding="utf8") as f:
res = f.read().split("\n")
if res:
return res
except Exception as Err:
print("open sql file error,information more check[0]".format(Err))
else:
print("sql file not exists")
列表拆分
# -*- coding: utf-8 -*-
# @File : list_cut.py
# @Date : 2022-03-05
# @Author : Richard
import os
import time
from math import ceil
class CutList(object):
def __init__(self, list_info=None, per_len=None):
self.list_info = list_info
self.per_len = per_len
def cut(self):
"""
列表切割,
:param list_info: 传入列的表
:param per_len: 切割的个数
:return: 返回切割后的列表
:return:
"""
try:
n = int(ceil(len(self.list_info) / float(self.per_len)))
return [self.list_info[i:i + n] for i in range(0, len(self.list_info), n)]
except Exception as Err:
print("列表切割失败!错误信息如下:".format(Err))
def list_cut(self):
"""
:param list_info: 传入列的表
:param per_list_len: 切割后的列表个数
:return: 返回列表
:return:
"""
try:
tmp_list = []
for i in range(0, len(self.list_info), self.per_len):
a = self.list_info[i:i + self.per_len]
tmp_list.append(a)
return tmp_list
except Exception as ErrE:
print("列表切割失败!错误信息如下:".format(ErrE))
数据库访问
# -*- coding: utf-8 -*-
# @File : get_cols.py
# @Date : 2021-11-25
# @Author : Richard
import os
import time
import dmPython
from RunSqlToDm.app.set_log import write_log
from RunSqlToDm.app.get_comp_info import GetCompName
from RunSqlToDm.app.get_pwd_info import GetPwd
class DB(object):
def __init__(self, sql=None, ip=None, user=None, port=None, comp_file=None, pwd_file=None):
"""
构造方法
:param sql: SQL语句,这里一条完整的SQL语句
:param ip: 数据库服务器地址
:param user: 数据库用户名
:param port: 数据库端口
:param comp_file: 单位字段文件
"""
self.sql = sql
self.ip = ip
self.user = user
self.port = port
self.comp_file = comp_file
self.pwd_file = pwd_file
def run_sql(self, guid=None, tena_id=None, system_ident=None, domain_name=None, pbx_code=None, pass_id=None,
meting_api=None, id7=None, id8=None):
"""
运行SQL
:return: 无返回值
"""
ip_list = ["190.45.163.100"]
get_pwd = GetPwd(file=self.pwd_file)
re_pwd = get_pwd.get_comp(user_name=str(self.user).upper())
# 密码选择
if self.ip in ip_list and str(self.user).upper() == "SYSDBA":
re_pwd = "DSdba@00"
elif self.ip not in ip_list and str(self.user).upper() == "SYSDBA":
re_pwd = "DSdba@11"
getCompName = GetCompName(file=self.comp_file)
try:
# 替换一些关键词
new_sql = str(self.sql).replace("#guid#", str(guid)).replace("#Tena_id#", str(tena_id)). \
replace("#system#", str(system_ident)).replace("#domain_name#", str(domain_name)). \
replace("#pbx_code#", str(pbx_code)).replace("#pass_id#", str(pass_id)). \
replace("#meting_api#", str(meting_api)).replace("#id7#", str(id7)).replace("#id8#", str(id8))
print("new_sql:\n{}".format(new_sql))
write_log(msg_info="即将在【{0}】执行:\n{1}".format(getCompName.get_comp(ip=self.ip), self.sql))
# print("sql:\n{0}".format(self.sql))
conn = dmPython.connect(user=self.user, password=re_pwd, server=self.ip, port=self.port)
cursor = conn.cursor()
# cursor.execute(self.sql)
cursor.execute(new_sql)
conn.commit()
cursor.close()
conn.close()
write_log(msg_info="【{0}】执行成功!\n".format(getCompName.get_comp(ip=self.ip)))
print("【{0}】执行成功!!\n".format(getCompName.get_comp(ip=self.ip)))
except Exception as Err:
print("【{0}】执行失败!!,错误信息:{1}".format(getCompName.get_comp(ip=self.ip), Err))
write_log("【{0}】执行失败! 单位:{1},IP:{2},详细SQL:\n{3}\n".format(getCompName.get_comp(ip=self.ip),
getCompName.get_comp(ip=self.ip), self.ip,
self.sql))
def run_mul(self, sqls, tena_id=None, com_name=None, domain_name=None, rowguid=None, system_ident=None,
pbx_code=None, pass_id=None, meting_api=None, id7=None, id8=None):
"""
# 运行SQL,多进程专用方法
:param sqls: SQL语句,是列表类型
:return: 无返回值
"""
ip_list = ["131.7.163.150"]
get_pwd = GetPwd(file=self.pwd_file)
re_pwd = get_pwd.get_comp(user_name=str(self.user).upper())
if self.ip in ip_list and str(self.user).upper() == "SYSDBA":
re_pwd = "DSdba@000"
elif self.ip not in ip_list and str(self.user).upper() == "SYSDBA":
re_pwd = "DSdba@111"
try:
getCompName = GetCompName(file=self.comp_file)
for j in sqls:
# 已经在主函数优化,不会走到这里
# 跳过空列表,主要是针对创建表的时候,若只有一个创建语句,在多核CPU的时候会切割出空列表,这里过滤掉可以避免出错
if not j:
continue
else:
# 替换一些关键词
new_sql = str(j).replace("#tena_id#", str(tena_id)).replace("#com_name#", str(com_name)) \
.replace("#domain_name#", str(domain_name)).replace("#rowguid#", str(rowguid)) \
.replace("#system#", str(system_ident)).replace("#pbx_code#", str(pbx_code)) \
.replace("#pass_id#", str(pass_id)).replace("#meting_api#", str(meting_api)) \
.replace("#id7#", str(id7)).replace("#id8#", str(id8))
try:
conn = dmPython.connect(user=self.user, password=re_pwd, server=self.ip, port=self.port)
cursor = conn.cursor()
write_log(msg_info="即将在【{0}】执行:\n{1}".format(getCompName.get_comp(ip=self.ip), new_sql))
cursor.execute(new_sql)
write_log(msg_info="【{0}】执行成功!:\n{1}".format(getCompName.get_comp(ip=self.ip), new_sql))
print("【{0}】 【{1}】 执行成功!".format(getCompName.get_comp(ip=self.ip), new_sql))
conn.commit()
cursor.close()
conn.close()
except Exception as Err_Ex:
write_log(
msg_info="【{0}】执行失败!,IP:{1},详细sql:{2},错误:".format(getCompName.get_comp(ip=self.ip), self.ip,
new_sql, Err_Ex))
print("SQL运行失败!错误信息:{0}".format(Err_Ex))
except Exception as Err:
print("SQL运行出错!错误信息:{0}".format(Err))
return False
总结
以上就是今天要讲的内容,本文仅仅简单介绍了Python3操作达梦数据库的使用,而达梦提供了大量能使我们快速便捷地处理数据的Python接口,详情请参考 https://eco.dameng.com/