管理和维护MySQL数据库有时会复杂化,有时任务需要繁琐甚至重复操作,鉴于这些因素,MySQL Utilities扩展是为了帮助初学者和经验丰富的数据库管理员执行常见任务。
MySQL Utilities内部
MySQL Utilities被设计成易于使用Python脚本,可以组合来提供更强大的功能。在内部,脚本使用mysql.utilities模块库来执行各项任务。由于该库是通用的函数,数据库管理员很容易创建自己的脚本来执行常见任务。这些工具都位于 /scripts文件夹下。
如果源码自带的工具满足不了你的需求,你完全可以私人订制。下面各节中讲呈现一个例子,来讨论解剖mysql.utilities模块库哪些可用。
MySQL Utilities剖析
MySQL Utilities使用了三层模块组织。在顶层是命令脚本,位于 /scripts 目录下。脚本中包含的命令模块设计来封装和隔离大部分工具的工作。命令模块位于/mysql/utilities/command目录下。命令模块的名称类似于脚本。一个命令模块包含一个或多个通用模块类和方法。通用模块位于 /mysql/utilities/common目录下。下面以mysqlserverinfo 为例。
|
/
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
|
每个工具脚本被设计成来处理用户输入和选项设置,并传递到命令模块。因此,脚本模块仅仅包含对这些选项的管理和验证的逻辑。操作的执行位于命令模块中。
命令模块被设计成使用其他Python的应用。例如,另一个Python脚本可以调用 serverinfo.py 模块中的方法。这使得开发人员创建自己的工具接口,也允许组合多个工具。例如,既要获取服务器信息同时也要磁盘的使用情况,可以将serverinfo.py 和 diskusage.py引入来创建一个新的工具来实现这一需求。
通用模块是MySQL Utilities库的核心。这些模块包含MySQL对象的抽象、驱动和机制。例如,一个服务类包含连接服务和执行查询的操作等等。
MySQL Utilities库
该库是不断变化的,下面列出的是当前常见的模块和主要1.0.1版本发布的类和方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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发行中默认的库,确保更简洁安装,增强可移植性,避免使用冷门的库文件,这样更好的接受和使用,也不需要根据不同平台依赖不同的库。
类方法和函数被设计成使用较少的必须参数和所有可选参数作为单个字典。如下面的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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
"
""
|
本实例是库中典型的方法和类。注意,该方法有三个必须的参数和一个可选的字典。
每个方法和函数使用这个机制来定义字典中的各自的默认值。快速浏览下方法文档中显示的字典的键名。在上面的例子中可用看出,字典中包含三个键和各自的默认值。
调用此方法和传递可选选项的不同值,可用这么做:
|
opt_dictionary
=
{
'width'
:
100
,
'iter'
:
10
,
'ok_to_fail'
:
False
,
}
result
=
do_something_wonderful
(
1
,
obj_1
,
obj_2
,
opt_dictionary
)
|
在整个库中都是使用上面方法的文档样式。
实例
通过上面的介绍,可能已经熟悉了MySQL Utilities和它所支持的模块库。下面来看看结合多个模块来解决问题的例子。
假设你要开发一个新的数据库解决方案,需要真是的数据和用户账号进行测试。mysqlserverclone 工具看起来是可行性的解决办法,但是只能克隆实例并不会复制数据的。然而,mysqldbcopy 可以复制数据,mysqluserclone 来复制用户,可以这么来实现。但是,追求懒惰是运维的至高境界,运维就是为了没有运维。因此,我们需要一条命令来解决。
好消息的是,这是可能的并且很容易实现的。首先将问题分成多个小部分,如下所示:
- 连接到原始服务器上
- 查找所有的数据库
- 查找所有的用户
- 克隆原始服务器
- 复制所有的库
- 复制所有的用户
下面来看看实现这个例子的代码:
第一个任务是连接到原始服务器。我们使用与MySQL Utilities相同的机制,通过指定--server选项。
|
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选项并获取连接参数值的字典。所有的诊断和错误都给我们实现了,因此,只需要进行检查,如下所示:
|
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,然后连接之。并再次进行异常检查,如下所示:
|
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模块时,你会注意到需要一大堆参数。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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 模块提供了一个克隆一个用户到一个或多个用户的方法,我们必须循环调用克隆账号。如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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)。
- 在复制过程中阻止新用户连接到服务器。
下节,看看这个场景的解决方案代码。