管理和维护MySQL数据库有时会复杂化,有时任务需要繁琐甚至重复操作,鉴于这些因素,MySQL Utilities扩展是为了帮助初学者和经验丰富的数据库管理员执行常见任务。
MySQL Utilities内部
MySQL Utilities被设计成易于使用Python脚本,可以组合来提供更强大的功能。在内部,脚本使用mysql.utilities模块库来执行各项任务。由于该库是通用的函数,数据库管理员很容易创建自己的脚本来执行常见任务。这些工具都位于 /scripts文件夹下。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
如果源码自带的工具满足不了你的需求,你完全可以私人订制。下面各节中讲呈现一个例子,来讨论解剖mysql.utilities模块库哪些可用。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
MySQL Utilities剖析
MySQL Utilities使用了三层模块组织。在顶层是命令脚本,位于 /scripts 目录下。脚本中包含的命令模块设计来封装和隔离大部分工具的工作。命令模块位于/mysql/utilities/command目录下。命令模块的名称类似于脚本。一个命令模块包含一个或多个通用模块类和方法。通用模块位于 /mysql/utilities/common目录下。下面以mysqlserverinfo 为例。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
/scripts/mysqlserverinfo.py | +--- /mysql/utilities/command/serverinfo.py | +--- /mysql/utilities/common/options.py | +--- /mysql/utilities/common/server.py | +--- /mysql/utilities/common/tools.py | +--- /mysql/utilities/common/format.py
每个工具脚本被设计成来处理用户输入和选项设置,并传递到命令模块。因此,脚本模块仅仅包含对这些选项的管理和验证的逻辑。操作的执行位于命令模块中。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
命令模块被设计成使用其他Python的应用。例如,另一个Python脚本可以调用 serverinfo.py 模块中的方法。这使得开发人员创建自己的工具接口,也允许组合多个工具。例如,既要获取服务器信息同时也要磁盘的使用情况,可以将serverinfo.py 和 diskusage.py引入来创建一个新的工具来实现这一需求。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
通用模块是MySQL Utilities库的核心。这些模块包含MySQL对象的抽象、驱动和机制。例如,一个服务类包含连接服务和执行查询的操作等等。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
MySQL Utilities库
该库是不断变化的,下面列出的是当前常见的模块和主要1.0.1版本发布的类和方法。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
Module Class/Method Description ---------- ------------------------- ---------------------------------------- database Database Perform database-level operations dbcompare get_create_object Retrieve object create statement diff_objects Diff definitions of two objects check_consistency Check data consistency of two tables format format_tabular_list Format list in either GRID or delimited format to a file format_vertical_list Format list in a vertical format to a file print_list Print list based on format (CSV, GRID, TAB, or VERTICAL) options setup_common_options Set up option parser and options common to all MySQL Utilities add_skip_options Add common --skip options check_skip_options Check skip options for validity check_format_option Check format option for validity add_verbosity Add verbosity and quiet options check_verbosity Check whether both verbosity and quiet options are being used add_difftype Add difftype option add_engines Add engine, default-storage-engine options check_engine_options Check whether storage engines listed in options exist parse_connection Parse connection values rpl Replication Establish replication connection between a master and a slave get_replication_tests Return list of replication test function pointers server get_connection_dictionary Get connection dictionary find_running_servers Check whether any servers are running on the local host connect_servers Connect to source and destination server Server Connect to running MySQL server and perform server-level operations table Index Encapsulate index for a given table as defined by SHOW INDEXES Table Encapsulate table for given database to perform table-level operations tools get_tool_path Search for MySQL tool and return its full path delete_directory Remove directory (folder) and contents user parse_user_host Parse user, passwd, host, port from user:passwd@host User Clone user and its grants to another user and perform user-level operations
通用接口规范和代码实践
MySQL Utilities代码使用主流的编码和通用的Python技术,使用Python发行中默认的库,确保更简洁安装,增强可移植性,避免使用冷门的库文件,这样更好的接受和使用,也不需要根据不同平台依赖不同的库。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
类方法和函数被设计成使用较少的必须参数和所有可选参数作为单个字典。如下面的方法:文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
def do_something_wonderful(position, obj1, obj2, options={}): """Does something wonderful A fictional method that does something to object 2 based on the location of something in object 1. position[in] Position in obj1 obj1[in] First object to manipulate obj2[in] Second object to manipulate options[in] Option dictionary width width of printout (default 75) iter max iterations (default 2) ok_to_fail if True, do not throw exception (default True) Returns bool - True = success, Fail = failed """
本实例是库中典型的方法和类。注意,该方法有三个必须的参数和一个可选的字典。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
每个方法和函数使用这个机制来定义字典中的各自的默认值。快速浏览下方法文档中显示的字典的键名。在上面的例子中可用看出,字典中包含三个键和各自的默认值。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
调用此方法和传递可选选项的不同值,可用这么做:文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
opt_dictionary = { 'width' : 100, 'iter' : 10, 'ok_to_fail' : False, } result = do_something_wonderful(1, obj_1, obj_2, opt_dictionary)
在整个库中都是使用上面方法的文档样式。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
实例
通过上面的介绍,可能已经熟悉了MySQL Utilities和它所支持的模块库。下面来看看结合多个模块来解决问题的例子。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
假设你要开发一个新的数据库解决方案,需要真是的数据和用户账号进行测试。mysqlserverclone 工具看起来是可行性的解决办法,但是只能克隆实例并不会复制数据的。然而,mysqldbcopy 可以复制数据,mysqluserclone 来复制用户,可以这么来实现。但是,追求懒惰是运维的至高境界,运维就是为了没有运维。因此,我们需要一条命令来解决。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
好消息的是,这是可能的并且很容易实现的。首先将问题分成多个小部分,如下所示:文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
- 连接到原始服务器上
- 查找所有的数据库
- 查找所有的用户
- 克隆原始服务器
- 复制所有的库
- 复制所有的用户
下面来看看实现这个例子的代码:文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
第一个任务是连接到原始服务器。我们使用与MySQL Utilities相同的机制,通过指定--server选项。文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
parser.add_option("--server", action="store", dest="server", type="string", default="root@localhost:3306", help="connection information for original server in " + \ "the form: <user>:<password>@<host>:<port>:<socket>")
一旦我们对选项和参数进行处理,连接服务器是很容易的。使用parse_connection 方法来获取server选项并获取连接参数值的字典。所有的诊断和错误都给我们实现了,因此,只需要进行检查,如下所示:文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
from mysql.utilities.common.options import parse_connection try: conn = parse_connection(opt.server) except: parser.error("Server connection values invalid or cannot be parsed.")
现在,我们有了连接参数,接下来,从server模块创建一个类实例Server,然后连接之。并再次进行异常检查,如下所示:文章源自运维生存时间-https://www.ttlsa.com/mysql/mysql-utilities-extend/
from mysql.utilities.common.server import Server server_options = { 'conn_info' : conn, 'role' : "source", } server1 = Server(server_options) try: server1.connect() except UtilError, e: print "ERROR:", e.errmsg
下一个步骤就是列出服务器上的所有数据库。我们使用新的server类实例来检索服务器上的所有数据库,如下所示:
db_list = [] for db in server1.get_all_databases(): db_list.append((db[0], None))
如果你想提供自己的数据库列表,可以使用类似于--databases选项,也可以添加else子句,来选择部分数据库,如--databases=db1,db2,db3。如下所示:
parser.add_option("-d", "--databases", action="store", dest="dbs_to_copy", type="string", help="comma-separated list of databases " "to include in the copy (omit for all databases)", default=None) if opt.dbs_to_copy is None: for db in server1.get_all_databases(): db_list.append((db[0], None)) else: for db in opt.dbs_to_copy.split(","): db_list.append((db, None))
注意,我们创建了一个元组列表,这是因为dbcopy 模块使用元组列表(old_db, new_db)来复制数据到一个新的库下。对于我们来说,并不希望重命名,因此设置为None。
接下来,我们希望得到所有用户列表。构建新的方案来更灵活的指定复制哪些用户。在这种情况下,我们还没有获取服务器上的所有用户,但是可以运行一个查询语句并对结果进行处理。如下所示:
users = server1.exec_query("SELECT user, host " "FROM mysql.user " "WHERE user != 'root' and user != ''") for user in users: user_list.append(user[0]+'@'+user[1])
现在,我们必须克隆原来的服务器,并创建一个新的实例。当你查看mysqlserverclone 代码时,你会发现它会调用 /mysql/utilities/command子目录下的另一个模块。这就使得你创建的新的组合工具可以直接调用。当你查看serverclone模块时,你会注意到需要一大堆参数。如下所示:
parser.add_option("--new-data", action="store", dest="new_data", type="string", help="the full path to the location " "of the data directory for the new instance") parser.add_option("--new-port", action="store", dest="new_port", type="string", default="3307", help="the new port " "for the new instance - default=%default") parser.add_option("--new-id", action="store", dest="new_id", type="string", default="2", help="the server_id for " "the new instance - default=%default") from mysql.utilities.command import serverclone try: res = serverclone.clone_server(conn, opt.new_data, opt.new_port, opt.new_id, "root", None, False, True) except exception.UtilError, e: print "ERROR:", e.errmsg sys.exit()
如你所见,操作是非常简单的。我们只需添加需要的选项,如--new-data, --new-port, --new-id(很像mysqlserverclone)并为其他参数提供一些默认值。
接下来,我们需要复制的数据库。我们再次使用命令模块mysqldbcopy来做所有的工作。首先,我们需要为新实例提供连接参数,这是一个字典形式的。因为是克隆的,一些值将是相同的。同样,指定数据目录。传递下面的字典到copy方法:
dest_values = { "user" : conn.get("user"), "passwd" : "root", "host" : conn.get("host"), "port" : opt.new_port, "unix_socket" : os.path.join(opt.new_data, "mysql.sock") }
在这种情况下,需要有多种选择以控制复制行为,如跳过某些对象。对于我们来说,希望复制所有的,因此只提供很少的设置,使用库的默认值。这个例子说明如何对脚本进行微调,以满足特定需求,而不必在脚本中添加大量的附加选项。我们启用quiet选项静默复制屏蔽复制过程信息,并跳过不存在的数据库(如果指定了--databases选项,并列出了一个不存在的数据库)。如下所示:
options = { "quiet" : True, "force" : True }
实际上,拷贝数据库是很容易的,只需调用方法并提供数据库列表。如下所示:
from mysql.utilities.command import dbcopy try: dbcopy.copy_db(conn, dest_values, db_list, options) except exception.UtilError, e: print "ERROR:", e.errmsg sys.exit()
最后,我们需要复制的用户账号。我们必须提供一个选项字典,并直接调用命令模块。在这种情况下,userclone 模块提供了一个克隆一个用户到一个或多个用户的方法,我们必须循环调用克隆账号。如下所示:
from mysql.utilities.command import userclone options = { "overwrite" : True, "quiet" : True, "globals" : True } for user in user_list: try: res = userclone.clone_user(conn, dest_values, user, (user,), options) except exception.UtilError, e: print "ERROR:", e.errmsg sys.exit()
如你所见,从MySQL Utilities命令和通用模块构建新的解决方案是很容易的,完全取决于你的思维。
实例改进
在后面内容中将贴出一个完整的解决方案实例copy_server.py,完全实现上面的需求。为了这个例子更加健壮性,可以考虑下面几点:
- 表锁: 目前,在拷贝数据库时没有锁定。为了实现数据库一致性的副本,可能要添加表锁定或使用事务(如果使用InnoDB)来确保数据一致性。
- 跳过不与要复制的数据库相关联的用户。
- 不复制只有全局权限的用户。
- 在所有用户复制后启动 replication (makes this example a clone and replicate scale out solution)。
- 在复制过程中阻止新用户连接到服务器。
下节,看看这个场景的解决方案代码。

评论