MySQL数据库一款非常优秀的开源数据库,很多人都在使用。我也不例外,数据库实例创建、数据库状态检测、数据库备份等,天天做着重复、枯燥的工作。为了减轻工作量,使用Python写了一个自动管理Mysql数据库的工具。
具体功能:
Mysql管理工具的主要功能:
> 数据库实例创建
> 数据库备份
> 数据库配置检测
> 数据库主从同步
> 配置文件重新载入
2. 工具代码结构:
library: 将共用的功能封装成一个库。
mysqlmanager:myman.py脚本实现基本管理Mysql的功能。
3. 工具代码展示
library/mysql.py:#!/usr/local/bin/python2.7
#-*- coding:utf-8 -*-
from ConfigParser import ConfigParser
import os
import MySQLdb
def getMyVariables(cur):
'''查询数据库配置信息'''
cur.execute('show global variables;')
data = cur.fetchall()
return dict(data)
class MySQLDConfig(ConfigParser):
'''将所有公用的功能封装成一个class'''
def __init__(self, config, **kw):
'''Python版本必须2.7以上,2.6版本没有allow_no_value 属性'''
ConfigParser.__init__(self, allow_no_value=True)
self.config = config
self.mysqld_vars = {}
if os.path.exists(self.config):
self.read(self.config)
self.get_mysqld_vars()
else:
self.set_mysqld_defaults_var()
self.set_mysqld_vars(kw)
def set_mysqld_vars(self, kw):
'''获取配置文件信息,覆盖默认配置'''
for k, v in kw.items():
self.mysqld_vars[k] = v
def get_mysqld_vars(self):
'''获取现有配置文件信息'''
options = self.options('mysqld')
rst = {}
for o in options:
rst[o] = self.get('mysqld', o)
self.set_mysqld_vars(rst)
def set_mysqld_defaults_var(self):
'''如果配置文件不存在,设置默认配置'''
defaults = {
"user":"mysql",
"pid-file": "/var/run/mysqld/mysqld.pid",
"socket": "/var/lib/mysql/mysql.sock",
"port": "3306",
"basedir": "/usr",
"datadir": "/tmp/mysql",
"tmpdir": "/tmp",
"skip-external-locking": None,
"bind-address": "127.0.0.1",
"key_buffer": "16M",
"max_allowed_packet": "16M",
"thread_stack": "192K",
"thread_cache_size": "8",
"myisam-recover": "BACKUP",
"query_cache_limit": "1M",
"query_cache_size": "16M",
"log_error": "/var/log/mysqld.log",
"expire_logs_days": "10",
"max_binlog_size": "100M"
}
self.set_mysqld_vars(defaults)
def save(self):
'''将配置信息保存至配置文件'''
if not self.has_section('mysqld'):
self.add_section('mysqld')
for k, v in self.mysqld_vars.items():
self.set('mysqld', k, v)
with open(self.config, 'w') as fd:
self.write(fd)
if __name__ == "__main__":
mc = MySQLDConfig('/root/david/mysqlmanager/cnfs/my.cnf', max_connection=200, user='mysql')
mc.set_var('skip-slave-start', None)
mc.save()
library/utils.py#!/usr/local/bin/python2.7
#-*-coding:utf-8 -*-
'''
格式时间的转换,数据库配置文件的单位(*.cnf)和数据库global(mysql>show global variables;)配置的单位不一致,需要转换
'''
unit = {'t':2**40,'g':2**30,'m':2**20,'k':2**10,'b':1}
def convertUnit(s):
s = s.lower()
lastchar = s[-1]
num = int(s[:-1])
if lastchar in unit:
return num*unit[lastchar]
else:
return int(s)
def scaleUnit(d):
for k,v in unit.items():
num = d / v
if (0
return num,k
mysqlmanager/myman.py:
#!/usr/local/bin/python2.7
#-*- coding:utf-8 -*-
from os import path
from optparse import OptionParser
from subprocess import PIPE, Popen
import MySQLdb
import glob
import os
import sys
import time
import datetime
import re
DIRNAME = path.dirname(__file__)
OPSTOOLS_DIR = path.abspath(path.join(DIRNAME, '..'))
sys.path.append(OPSTOOLS_DIR)
from library.mysql import MySQLDConfig, getMyVariables
REPLICATION_USER = 'repl'
REPLICATION_PASS = '123qwe'
MYSQL_DATA_DIR = '/home/david/data'
MYSQL_CONF_DIR = '/home/david/cnfs'
MYSQL_BACK_DIR = '/home/david/backup'
def opts():
parser = OptionParser(usage="usage: %prog [options] arg1 arg2")
parser.add_option("-c","--cmd",
dest="cmd",
action="store",
default="check",
help="Check the configuration file and database configuration parameters are different.[%options]"
)
parser.add_option("-n","--name",
dest="name",
action="store",
default="mysqlinstance",
help="Create Examples."
)
parser.add_option("-p","--port",
dest="port",
action="store",
default="3306",
help="Examples of port."
)
return parser.parse_args()
def checkPort(d, p):
'''实例端口检测'''
for m in d:
if p == m.mysqld_vars['port']:
return True
return False
def setReplMaster(cur):
'''设置slave数据库同步用户的授权'''
sql = "GRANT REPLICATION SLAVE ON *.* TO %s@'localhost' IDENTIFIED BY '%s'" % (REPLICATION_USER, REPLICATION_PASS)
cur.execute(sql)
def connMySQLd(mc):
'''连接数据库'''
host = '127.0.0.1'
user = 'root'
port = int(mc.mysqld_vars['port'])
conn = MySQLdb.connect(host, port=port, user=user)
cur = conn.cursor()
return cur
def run_mysql(cnf):
'''运行数据库'''
cmd = "mysqld_safe --defaults-file=%s &" % cnf
p = Popen(cmd, stdout=PIPE, shell=True)
time.sleep(5)
return p.returncode
def setOwner(p, user):
'''设置目录权限'''
os.system("chown -R %s:%s %s" % (user, user, p))
def mysql_install_db(cnf):
'''数据库初始化'''
p = Popen("mysql_install_db --defaults-file=%s" % cnf, stdout=PIPE, shell=True)
#p = Popen("mysql_install_db --user=mysql --datadir=%s " % MYSQL_DATA_DIR, stdout=PIPE, shell=True)
stdout, stderr = p.communicate()
return p.returncode
def _genDict(name, port):
'''设置文件存储目录及监听端口'''
return {
'pid-file': path.join(MYSQL_DATA_DIR, name, "%s.pid" % name),
'socket': '/tmp/%s.sock' % name,
'port': port,
'datadir': path.join(MYSQL_DATA_DIR, name)+'/',
'log_error': path.join(MYSQL_DATA_DIR, name)
}
def readConfs():
'''读取配置文件,如果配置文件不存在,使用默认配置生成配置文件'''
confs = glob.glob(path.join(MYSQL_CONF_DIR, '*.cnf'))
return [MySQLDConfig(c) for c in confs]
def getCNF(name):
'''获取配置文件完整路径'''
return path.join(MYSQL_CONF_DIR, "%s.cnf" % name)
def runMySQLdump(cmd):
'''启动Mysql命令'''
p = Popen(cmd, stdout=PIPE, shell=True)
stdout, stderr = p.communicate()
return p.returncode
def getBinlogPOS(f):
'''获取binlog'''
with open(f) as fd:
f, p = findLogPos(l)
if f and p:
return f,p
def findLogPos(s):
rlog = re.compile(r"MASTER_LOG_FILE='(\S+)',", re.IGNORECASE)
rpos = re.compile(r"MASTER_LOG_POS=(\d+),?", re.IGNORECASE)
log = rlog.search(s)
pos = rpos.search(s)
if log and pos:
return log.group(1), int(pos.group(1))
else:
return (None, None)
def changeMaster(cur, host, port, user, mpass, mf, p):
sql = '''CHANGE MASTER TO
MASTER_HOST='%s',
MASTER_PORT='%s',
MASTER_USER='%s',
MASTER_PASSWORD='%s',
MASTER_LOG_FILE='%s',
MASTER_LOG_POS=%s;''' % (host, port, user, mpass, mf, p)
cur.execute(sql)
def createInstance(name, port, dbtype="master", **kw):
'''创建数据库实例'''
cnf = path.join(MYSQL_CONF_DIR, "%s.cnf" % name)
datadir = path.join(MYSQL_DATA_DIR, name)
exists_cnfs = readConfs()
if checkPort(exists_cnfs, port):
print >> sys.stderr, "port exist."
sys.exit(-1)
if not path.exists(cnf):
c = _genDict(name, port)
c.update(kw)
mc = MySQLDConfig(cnf, **c)
mc.save()
else:
mc = MySQLDConfig(cnf, **kw)
if not path.exists(datadir):
mysql_install_db(cnf)
setOwner(datadir, mc.mysqld_vars['user'])
run_mysql(cnf)
time.sleep(3)
cur = connMySQLd(mc)
setReplMaster(cur)
def diffVariables(instance_name):
'''查询数据库配置文件和数据库配置的差异'''
cnf = getCNF(instance_name)
if path.exists(cnf):
mc = MySQLDConfig(cnf)
print mc
cur = connMySQLd(mc)
vars = getMyVariables(cur)
for k, v in mc.mysqld_vars.items():
k = k.replace('-', '_')
if k in vars and vars[k] != v:
print k, v, vars[k]
def setVariable(instance_name, variable, value):
'''重新加载配置'''
cnf = getCNF(instance_name)
if path.exists(cnf):
mc = MySQLDConfig(cnf)
cur = connMySQLd(mc)
cur.execute('set global %s = %s' % (variable, value))
mc.set_var(variable, value)
mc.save()
def backupMySQL(instance_name):
'''备份数据库'''
cnf = getCNF(instance_name)
if path.exists(cnf):
mc = MySQLDConfig(cnf)
now = datetime.datetime.now()
timestamp = now.strftime('%Y-%m-%d-%H%M%S')
backup_file = path.join(MYSQL_BACK_DIR, instance_name, timestamp+'.sql')
_dir = path.dirname(backup_file)
if not path.exists(_dir):
os.makedirs(_dir)
cmd = 'mysqldump -A -x -F --master-data=1 --host=127.0.0.1 --user=root --port=%s > %s' % (mc.mysqld_vars['port'], backup_file)
runMySQLdump(cmd)
def restoreMySQL(instance_name, instance_port, sqlfile, **kw):
createInstance(instance_name, instance_port, **kw)
cnf = getCNF(instance_name)
if path.exists(cnf):
mc = MySQLDConfig(cnf)
cur = connMySQLd(mc)
cmd = "mysql -h 127.0.0.1 -P %s -u root
f, p = getBinlogPOS(sqlfile)
runMySQLdump(cmd)
changeMaster(cur,
host=kw['master-host'],
port=kw['master-port'],
user=REPLICATION_USER,
mpass=REPLICATION_PASS,
mf=f,
p=p)
def _init():
'''查询mysql几个目录是否存在,如果不存在,自动创建'''
if not path.exists(MYSQL_DATA_DIR):
os.makedirs(MYSQL_DATA_DIR)
if not path.exists(MYSQL_CONF_DIR):
os.makedirs(MYSQL_CONF_DIR)
if not path.exists(MYSQL_BACK_DIR):
os.makedirs(MYSQL_BACK_DIR)
def main():
opt, args = opts()
instance_name = opt.name
instance_port = opt.port
command = opt.cmd
if command == "create":
if not args:
createInstance(instance_name, instance_port)
else:
dbtype = args[0]
serverid = args[1]
mysqld_options = {'server-id':serverid}
if dbtype == 'master':
mysqld_options['log-bin'] = 'mysql-bin'
elif dbtype == 'slave':
master_host = args[2]
master_port = args[3]
mysqld_options['master-host'] = master_host
mysqld_options['master-port'] = master_port
mysqld_options['master-user'] = REPLICATION_USER
mysqld_options['master-password'] = REPLICATION_PASS
mysqld_options['skip-slave-start'] = None
mysqld_options['replicate-ignore-db'] = 'mysql'
mysqld_options['read-only'] = None
createInstance(instance_name, instance_port, dbtype=dbtype, **mysqld_options)
elif command == 'check':
diffVariables(instance_name)
elif command == 'adjust':
variable = args[0]
value = args[1]
setVariable(instance_name, variable, value)
elif command == 'backup':
backupMySQL(instance_name)
elif command == 'restore':
serverid == args[0]
mhost = args[1]
mport = args[2]
sqlfile = args[3]
mysqld_options = {
"master-host":mhost,
"master-port":mport,
"server-id":serverid,
"skip-slave-start":None,
}
restoreMySQL(instance_name, instance_port, sqlfile, **mysqld_options)
if __name__ == "__main__":
print main()
4.测试
帮助信息:
创建master实例:
创建slave实例:
检测配置文件和数据库加载配置差异:
由于单位格式不同,所以出现了差异,可以结合library/utils.py 进行单位换算
数据库备份:
需要注意:
1. python版本必须2.7及2.7以上版本。
2. MYSQL_DATA_DIR 目录不能放在/root目录下,如果放在root目录下,初始化数据库的时候会报错(权限问题)。我在这犯过错.
如果哪里有错误,或者不足的地方。还请大家多多沟通。