Posts tagged ‘mysql’

xtrabackup 多实例MySQL备份

花了两个晚上,10来个小时,来测试如何让xtrabackup在多实例的MySQL上成功备份出来,最终找到了一个办法:–defaults-file=”fake/my.cnf.1″,也就是传递一个假的mysql配置文件,让它读取对应的实例数据,备份出来。这样,对每个实例做一个假的mysql配置文件,传给它让它备份出来。

其实我想到的第一个办法就是这个办法。但是作为程序员出身的我,有点追求完美的程序员,始终感觉这种方法太臭了。于是我开始尝试:
1、修改innobackupex-1.5.1的perl文件。
发现innobackupex-1.5.1脚本里面mysqld是写死的,也就是说它就只支持一个实例。好吧,那我就把mysqld全部修改成参数,然后通过参数传递进去(顺便再次温习了一下perl的GetOptions函数,呵呵)。这样总行把?不行。测试的结果报错,说
fatal error: OR no ‘datadir’ option in group ‘mysqld1′ in MySQL options
对应的代码如下:
if (!exists $config{$group}) {
# no group
print STDERR “$prefix fatal error: no ‘$group’ group in MySQL options\n”;
print STDERR “$prefix fatal error: OR no ‘datadir’ option in group ‘$group’ in MySQL options\n”;
exit(1);
}
而检查了以后发现是另外一个问题:
xtrabackup: Error: Please set parameter ‘datadir’
xtrabackup报错了,恩,只好仔细看看文件看看脚本了,发现read_config_file函数里面这么说的:
if ($option_defaults_file) {
$options = $options . ” –defaults-file=\”$option_defaults_file\” “;
}

$options = $options . “–print-param”;

# read file to an array, one line per element
#file_to_array($filename, \@lines);
$cmdline = “$option_ibbackup_binary $options”;
@lines = `$cmdline`;
大家特别注意:”$option_ibbackup_binary $options”,翻译成白话文就是xtrabackup [--defaults-file=#] –print-param,也就是说innobackupex-1.5.1它自己没有处理mysql配置文件的地方,他让xtrabackup去处理,它只管拿现成的。他把这些东西通通放到hash map里面,通过get_option从hash里面获得datadir,innodb_data_home_dir等一系列信息。而xtrabackup解析文件的时候就报错,找不到mysqld组,所以,修改innobackupex-1.5.1是不可行的。

2、如果xtrabackup报错,那么修改xtrabackup的源文件来让它不报错可不可以列?
修改源码的事情比较不靠谱,就算我修改了,并且运行良好,xtrabackup以后的更新和bug修复我就赶不上了。先看看把,也许修改起来简单的。一看,虽然就一个xtrabackup.c文件,但是没有找到比较好下手的地方。罢了罢了,回归到骗人的把戏上吧。

3、骗人的把戏
查看xtrabackup的时候,倒是发现可以在my.cnf上配置xtrabackup的组,把备份的一些参数信息这样传给它。现在就有两个办法,
a、在mysql配置文件中放一个xtrabackup的组,把各个实例的配置信息轮询的更新到组里面,让xtrabackup备份
b、针对每个实例新建一个假的mysql配置文件,让xtrabackup的备份每次读取不同的文件来备份
呵呵,当然是第二个办法好了,于是测试开始,妈的,一直出错,传递的–default-file文件给它,它就是不读,倔强的报错。最后竟然发现是–defaults-file少了一个s,悲剧。好,这个搞定以后就没有什么问题了。建立两个假的mysql配置文件,让它备份把。
[pickup.lichun@alibaba-26841 xtrabackup-1.2]$ xtrabackup –defaults-file=’/tmp/my.cnf’ –print-param
# This MySQL options file was generated by XtraBackup.
[mysqld]
datadir = “/data/mysqldata1/mydata”
tmpdir = “/data/mysqldata1/tmpdir/”
innodb_data_home_dir = “/data/mysqldata1/innodb_ts”
innodb_data_file_path = “ibdata1:256M:autoextend”
innodb_log_group_home_dir = “/data/mysqldata1/innodb_log”
innodb_log_files_in_group = 2
innodb_log_file_size = 536870912

may your success.

admin账户下安装mysql

安装MySQL相信大家都会安装,但是如果现在你只有机器的admin账户,你不能够su到root下去,那么你怎么才能安装好mysql列。
至少我安装起来感觉缚手缚脚的,最终还是安装成功运行起来了,特别把一些需要注意的点记录下来。

1、my.cnf放在哪里?
从reference上抄录如下:
On Unix, Linux and Mac OS X, MySQL programs read startup options from the following files, in the specified order (top items are used first).
File Name     Purpose
/etc/my.cnf     Global options
/etc/mysql/my.cnf     Global options (as of MySQL 5.1.15)
SYSCONFDIR/my.cnf     Global options
$MYSQL_HOME/my.cnf     Server-specific options
defaults-extra-file     The file specified with –defaults-extra-file=path, if any
~/.my.cnf     User-specific options

~ represents the current user’s home directory (the value of $HOME).
前面的那些目录都没有权限,那么就只好在$HOME下放一个.my.cnf

2、目录配置
目录当然需要放在/home/admin下了,我这边的目录在[mysqld]组下设置如下:
basedir=/home/admin/mysql/mysql/
datadir=/home/admin/mysql/mysqldata/mydata
socket=/home/admin/mysql/mysqldata/sock/mysql.sock
pid-file=/home/admin/mysql/mysqldata/sock/mysql.pid
tmpdir=/home/admin/mysql/mysqldata/tmpdir
log-error=/home/admin/mysql/mysqldata/log/error.log
slow_query_log_file=/home/admin/mysql/mysqldata/log/slow-query.log
log-bin=/home/admin/mysql/mysqldata/binlog/mysql-bin
relay-log=/home/admin/mysql/mysqldata/relaylog/mysql-relay-bin
innodb_data_home_dir = /home/admin/mysql/mysqldata/innodb_ts
innodb_log_group_home_dir = /home/admin/mysql/mysqldata/innodb_log
本来basedir没有在这个.my.cnf里面,结果就出错了。
唉,还好 ./support-files/mysql.server 和 mysqld_safe是脚本,用bash -x 调试模式执行的时候仔细看还是能够找到对应的错误

对应的[client]需要指定:
socket = /home/admin/mysql/mysqldata/sock/mysql.sock

3、用户配置:
用户之前是用的mysql账户运行的,现在要用admin运行,所以修改[mysqld]组如下:
user=admin

4、./support-files/mysql.server修改
作为起停MySQL的脚本,它的basedir和datadir我都设置了
basedir=/home/admin/mysql/mysql/
datadir=/home/admin/mysql/mysqldata/mydata/

5、[mysqld_safe]设置
pid-file=/home/admin/mysql/mysqldata/sock/mysql.pid
本来我只设置了pid,并且在一台机器上启动MySQL成功了。但是在第二台机器上启动的时候失败了。
找了半天,才在mysqld_safe脚本里面找到出错的地方:
/home/admin/mysql/mysql//bin/mysqld_safe: line 137: /var/log/mysqld.log: Permission denied
对应的错误日志不对,于是,在.my.cnf的[mysqld_safe]组里加上
log-error=/home/admin/mysql/mysqldata/log/error.log
启动MySQL,没有异常。大功告成!

另外,也要注意把MySQL的bin目录加入到PATH里面去!

其实这里面最需要注意的就是出错的时候:
bash -x ./support-files/mysql.server start
或者
bash -x /home/admin/mysql/mysql//bin/mysqld_safe –datadir=/home/admin/mysql/mysqldata/mydata –pid-file=/home/admin/mysql/mysqldata/sock/mysql.pid &
查看到底是哪里出错了。根据具体的错误来定位和修改。

may your success!

双slave的server_id相同的问题

前段时间新来了一个机器,想要替换一个应用的备机。
已有的架构是这样的:A和B两个MySQL相互作为备机,A的性能比较好,B的性能比较差。
现在买了一台新的机器,新的机器c性能比较好,准备用来替换B。
安装MySQL和配置等等都不细说,比较顺利的。

但是安装好以后,配置复制的时候出现了问题,change master以后可以复制,但是数据库监控一直报错。
show slave status\G显示的Seconds_Behind_Master:的复制延迟一下子是0,一下子是90多w秒。
以前我们也遇到过类似的问题,具体原因不大记得了(唉,就是没有文档记录的错阿),最后好像是通过重做复制修复了。
尝试重新change master以后还是没有好转。查看错误日志显示如下:
100817 19:45:45 [Note] Slave I/O thread: Failed reading log event, reconnecting to retry, log ‘mysql-bin.000033′ at postion 271148235
100817 19:45:45 [Note] Slave: received end packet from server, apparent master shutdown:
100817 19:45:45 [Note] Slave I/O thread: Failed reading log event, reconnecting to retry, log ‘mysql-bin.000033′ at postion 271148235
100817 19:45:45 [Note] Slave: received end packet from server, apparent master shutdown:
并且不断的在刷,每秒钟有十来条记录。
通过对应的binlog file和binlog position去查看主机对应的记录都没有问题。并且主机到备机的复制只是时断时续,并没有一直停着不往前走。
实在没有办法,最后停掉了复制。这个事情因为并不是那么紧急,所以也暂时放在一边了。

偶尔的机会,我在备机B上也发现了同样的错误,也是Slave: received end packet from server, apparent master shutdown的错误。
并且change master以后,出现的频率变的非常高。
看了一下my.cnf配置文件,注意到B和C配置的server_id都是2,因为C当时是计划替换B的所以server_id也配置为一样了。
是不是这个问题引起的列?试试就知道了,把C的server_id配置成3,数据库B和数据库C检查复制状态,好了!

想起数据库复制的那一块代码。数据库连接master,请求复制开始的时候需要几个参数:
1、四个字节的数据文件开始位置
2、两个字节的binlog flag(目前还没有用上)
3、2个字节的binlog file名字字符串长度
4、4个字节的server_id
5、n个字节的binlog file名字字符串(在前面3里记录了它的长度)
这里向主机申请的时候,需要填写自己的server_id,来通知master连上来的是谁。
如果现在有两个slave连上来并都申明自己的server_id为2,MySQL的master就有点无所适从了。
初步怀疑是slave的IO线程连上以后,master过了一段时间(ms级别的)才发现,然后把新的连接断掉,所以slave io线程才会报警:
100817 19:45:45 [Note] Slave I/O thread: Failed reading log event, reconnecting to retry, log ‘mysql-bin.000033′ at postion 271148235
100817 19:45:45 [Note] Slave: received end packet from server, apparent master shutdown:
说收到主机发起的end packet(主机每次发送一个event过来,如果1、收到的包数据有误或者2、不能由接收到的包解出正确的event来,slave io判定复制有问题,
而其中end packet属于第一种情况
对应的代码为sql/slave.cc的read_event函数:
/* Check if eof packet */
if (len < 8 && mysql->net.read_pos[0] == 254)
{
sql_print_information(“Slave: received end packet from server, apparent ”
“master shutdown: %s”,
mysql_error(mysql));
DBUG_RETURN(packet_error);
}

由于MySQL的io有自动重连的机制,所以MySQL的io线程又连主机,并且还连上了。
master过了一段时间(ms级别的)又发现已经有了server_id=2的slave连着的,把它重新断掉了。
看了一下master的代码sql/sql_parse.cc的dispatch_command函数:
mysql_binlog_send(thd, thd->strdup(packet + 10), (my_off_t) pos, flags);
unregister_slave(thd,1,1);
发送binlog以后,它会做一个unregister_slave,它认为是同一个slave change master连接过来,“理所当然的”它就把前面的连接给干掉了。
周而复始,就产生了这样的错误。
解决的办法很简单,就是不要用同样的server_id的slave同时连一台master。server_id可以设置的很大,所以不要顾虑,赶紧重设吧。

不知道有没有人想过为什么server_id不能设置为0
在my.cnf里面是没有办法设置server_id=0的,不信的话,你试试,正常情况下,它不会让你start slave的。代码如下sql/slave.cc的start_slave_thread函数:
if (!server_id)
{
if (start_cond)
pthread_cond_broadcast(start_cond);
if (start_lock)
pthread_mutex_unlock(start_lock);
sql_print_error(“Server id not set, will not start slave”);
DBUG_RETURN(ER_BAD_SLAVE);
}
不过,我还真的没有测试过这种情况,呵呵。
当然,server_id设置为0的情况也有,但是不是你或者我,而是mysqlbinlog,它的server_id可以设置为0.
原因如下:如果mysqlbinlog的server_id为0的话,它就会把正常的slave给停掉了。万一它的server_id为5,它连上去以后就会把真正的server-id为5的slave断掉了。
server_id设置为0我印象中还有一个不同点,是关于MySQL master发送binlog是否会停止的,但是就是想不起来了,老了。代码也没有找到确凿的证据;先这样把

may your success!

my.cnf的[mysql]组放些什么

my.cnf里面有很多组,[mysqld],[client],[mysql],[mysqldump]等等。
mysql算是我们用的最多的工具,管理,数据订正,查看状态。。。
而每次mysql客户端启动的时候都会读取[mysql]的参数。大家都是怎么设置的列?
先说一下我的:
no-auto-rehash
prompt=”\\u@\\h : \\d \\R:\\m:\\s> ”
#tee=”/tmp/query.log”
#pager=”less -i -n -S”
max_allowed_packet = 4M
show-warnings
default-character-set = UTF8

no-auto-rehash是为了不让MySQL自动提示,这样对MySQL客户端有点慢。
#tee=”/tmp/query.log”是把你输入的SQL等导入到/tmp/query.log文件,如果你不怕这个文件太大可以设置这个参数
#pager=”less -i -n -S”是设置MySQL显示分页数据的工具,如果你安装了maatkit的visual EXPLAIN工具,可以设置为它,正常的情况下它表现和less一样,如果是显示查询计划,它就与众不同了。
max_allowed_packet = 4M就不说了

其他的三个我想特别说一下:
prompt=”\\u@\\h : \\d \\R:\\m:\\s> ” 这个是设置提示符用的,让你随时可以知道你在那个机器,那个库,时间等等,比如这个prompt对应的就是:root@localhost : mysql 10:21:24>。
show-warnings 可以让MySQL执行SQL出现warning的时候,把warning也打印出来,这个在你执行多条语句,但是第一条语句就有warning的时候非常有效。这个参数也可以减轻你的劳动力,每次warning出现的时候,你不用自己手工去输入命令show warnings;
default-character-set = UTF8 其实在client可能我们大部分同志会加这个。但是为什么在这里还要加一个列。mysqlbinlog不认default-character-set这个参数,有时候我们没有办法,只好注释掉default-character-set,如果mysqlbinlog查询binlog之后不记得恢复,那么你下次打开MySQL客户端的时候,你的客户端字符集就有可能没有设置。(这个问题也可以用loose_default-character-set来解决),但是在[mysql]里面加了这个以后,我们就没有这个担心了。这个参数我建议加到其他的客户端组比如[mysqldump]等。

may your success.

select into outfile access deny问题

为应用建立了rnd的帐号,专门为他们查询线上数据库用的,当然,只有他们上了生产网络以后才能连上数据库,安全方面我们还是很注意的,呵呵。
授权的语句如下:
grant select on armory.* to rnd;
flush privileges;

select查询数据没有问题,但是有的用户有了更多的需求,他想把数据导出来,简单的处理的话,可以用select into outfile导出来。自己指定字段的分隔,行分隔等等。
但是用户一查询就报:access deny的错误,权限不对。
rnd@localhost : armory 09:26:31> select * into outfile ‘/tmp/1.txt’ from os limit 5;
ERROR 1045 (28000): Access denied for user ‘rnd’@'%’ (using password: NO)
郁闷的是MySQL没有说缺少了那个权限。

在本机测试了一下,
grant all on armory.* to rnd;
flush privileges;
给rnd所有的权限以后,还是报权限错误。这个就奇怪了,所有的权限都给它了,还报错?不可理喻阿。
实在搞不定,最后让用户:
mysql -urnd -p -e ‘select * from os limit 5;’ >1.txt
的变通方法。

一直被这个纠结着,突然后来有一天,查询了一下MySQL的文档,找到是file的权限没有加上去,但是当时MySQL对应的库的所有权限我都加上去了阿。灵光一闪,file是全局的权限,在MySQL中对单个库是没有这个权限概念的,所以就算我把库上的所有权限给了rnd,file的权限其实还是没有附权给它的。不信的话,我们这就试试:
root@localhost : (none) 09:55:14> grant file on armory.* to rnd;
ERROR 1221 (HY000): Incorrect usage of DB GRANT and GLOBAL PRIVILEGES
果然,File权限是GLOBAL权限,不能附权给数据库。
GLOBAL FILE附权以后
root@localhost : mysql 09:58:21> grant file on *.* to rnd;
Query OK, 0 rows affected (0.00 sec)
select查询就可以执行了:
rnd@localhost : armory 10:00:42> select * into outfile ‘/tmp/1.txt’ from os limit 5;
Query OK, 5 rows affected (0.00 sec)

其实MySQL的权限可能比较拗,让我们一下子适应不过来。MySQL的权限可以精细到列,权限判断是根据GLOBAL,DB,TABLE,COLUMN来授权的,可以简单的理解为他们对应到mysql库中的四个表:user,db,tables_priv,columns_priv这几个表。当然,MySQL没有这么简单拉。有兴趣的话可以好好看一下MySQL的reference或者其他介绍。举个例子:
rnd@localhost : armory 10:00:43> show grants for rnd;
+————————————————-+
| Grants for rnd@%                                |
+————————————————-+
| GRANT FILE ON *.* TO ‘rnd’@'%’                  |
| GRANT ALL PRIVILEGES ON `armory`.* TO ‘rnd’@'%’ |
+————————————————-+
2 rows in set (0.00 sec)
grant对同一个用户就分了两行,分别对应着user和db里面的两行:
root@localhost : mysql 10:18:34> select * from mysql.user where user=’rnd’\G
*************************** 1. row ***************************
Host: %
User: rnd
Password:
Select_priv: N
Insert_priv: N
Update_priv: N
Delete_priv: N
Create_priv: N
Drop_priv: N
Reload_priv: N
Shutdown_priv: N
Process_priv: N
File_priv: Y
Grant_priv: N
References_priv: N
Index_priv: N
Alter_priv: N
Show_db_priv: N
Super_priv: N
Create_tmp_table_priv: N
Lock_tables_priv: N
Execute_priv: N
Repl_slave_priv: N
Repl_client_priv: N
Create_view_priv: N
Show_view_priv: N
Create_routine_priv: N
Alter_routine_priv: N
Create_user_priv: N
Event_priv: N
Trigger_priv: N
ssl_type:
ssl_cipher:
x509_issuer:
x509_subject:
max_questions: 0
max_updates: 0
max_connections: 0
max_user_connections: 0
1 row in set (0.00 sec)

root@localhost : mysql 10:18:41> select * from mysql.db where user=’rnd’\G
*************************** 1. row ***************************
Host: %
Db: armory
User: rnd
Select_priv: Y
Insert_priv: Y
Update_priv: Y
Delete_priv: Y
Create_priv: Y
Drop_priv: Y
Grant_priv: N
References_priv: Y
Index_priv: Y
Alter_priv: Y
Create_tmp_table_priv: Y
Lock_tables_priv: Y
Create_view_priv: Y
Show_view_priv: Y
Create_routine_priv: Y
Alter_routine_priv: Y
Execute_priv: Y
Event_priv: Y
Trigger_priv: Y
1 row in set (0.00 sec)
MySQL的权限检查也是通过检查这两个表来进行判断的。

附上tables_priv和columns_priv的两个表的字段,和user和db还是有点不同的,用到了set类型。
root@localhost : mysql 10:18:58> desc tables_priv;
+————-+———————————————————————————————————————————–+——+—–+——————-+—————————–+
| Field       | Type                                                                                                                              | Null | Key | Default           | Extra                       |
+————-+———————————————————————————————————————————–+——+—–+——————-+—————————–+
| Host        | char(60)                                                                                                                          | NO   | PRI |                   |                             |
| Db          | char(64)                                                                                                                          | NO   | PRI |                   |                             |
| User        | char(16)                                                                                                                          | NO   | PRI |                   |                             |
| Table_name  | char(64)                                                                                                                          | NO   | PRI |                   |                             |
| Grantor     | char(77)                                                                                                                          | NO   | MUL |                   |                             |
| Timestamp   | timestamp                                                                                                                         | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| Table_priv  | set(‘Select’,'Insert’,'Update’,'Delete’,'Create’,'Drop’,'Grant’,'References’,'Index’,'Alter’,'Create View’,'Show view’,'Trigger’) | NO   |     |                   |                             |
| Column_priv | set(‘Select’,'Insert’,'Update’,'References’)                                                                                      | NO   |     |                   |                             |
+————-+———————————————————————————————————————————–+——+—–+——————-+—————————–+
8 rows in set (0.00 sec)

root@localhost : mysql 10:21:24> desc columns_priv;
+————-+———————————————-+——+—–+——————-+—————————–+
| Field       | Type                                         | Null | Key | Default           | Extra                       |
+————-+———————————————-+——+—–+——————-+—————————–+
| Host        | char(60)                                     | NO   | PRI |                   |                             |
| Db          | char(64)                                     | NO   | PRI |                   |                             |
| User        | char(16)                                     | NO   | PRI |                   |                             |
| Table_name  | char(64)                                     | NO   | PRI |                   |                             |
| Column_name | char(64)                                     | NO   | PRI |                   |                             |
| Timestamp   | timestamp                                    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| Column_priv | set(‘Select’,'Insert’,'Update’,'References’) | NO   |     |                   |                             |
+————-+———————————————-+——+—–+——————-+—————————–+
7 rows in set (0.00 sec)

may your success.

一个目录chmod造成的血案

最近团队的可用性一直没有上去,把自己之前差点造成故障的一个事情翻出来,好好看一下,提高警惕。

2010年5月25日。艳阳高照。但是我的心却是拔凉拔凉的。执行了一个授权和chmod的操作,由于是批量执行,结果导致大量MySQL数据库报警,出现了重大的问题。
事情得从头说起。为了配合sa部门管理机器和收回主机root权限,我们想去将admin用户添加到mysql组里,并统一一下MySQL数据库的目录,以及相关的权限。于是我写了一个简单的脚本如下:

id admin
usermod -G mysql admin
id admin
groups admin

chown -R mysql:mysql /var/lib/mysql
chown -R mysql:mysql /home/mysql
chown -R mysql:mysql /data/mysqldata

chmod -R 770 /var/lib/mysql
chmod -R 770 /var/lib/mysql/etc
chmod -R 770 /var/lib/mysql/log
chmod -R 770 /var/lib/mysql/sock

chmod 750 /data
chmod 750 /data/mysqldata
chmod 700 /data/mysqldata/mydata
chmod 700 /data/mysqldata/innodb_log
chmod 700 /data/mysqldata/innodb_ts
chmod 750 /data/mysqldata/binlog
chmod 750 /data/mysqldata/relaylog
chmod 750 /data/mysqldata/tmpdir

按照惯例,我提交了变更,简单测试了一下,并在pm-my1b生产机器上先执行了一下,看看,没有什么问题。然后就分发到所有的pm,offer,comm,ai等一批机器上去。于是悲剧产生了,数据库报警。一看MySQL
的报警,
sort aborted
这个报警是由于mysql访问它的tmpdir出现问题导致的。判断是由于文件权限导致的。赶紧修改脚本,给刚才附权的所有目录修改为750.后来一想,还是不放心。干脆把所有的权限都给它们了。777。部分数据库没有报警了。

我们认真检查了一下数据库。发现部分MySQL不能写自己的日志。show master logs显示
root@localhost : (none) 10:24:01> show master logs;
ERROR 1381 (HY000): You are not using binary logging
没有打开binlog写日志的功能。应该就是由于目录权限的问题,MySQL自动把写binlog日志的功能停掉了;而它对应的备机则由于这个问题,show slave status\G会卡死在那边。这样的话,如果不写binlog的MySQL数据库是应用访问的机器,那么现在它就是单点(备机无法获得binlog日志,自然无法同步数据),这种情况对应用来说其实是不可以接受的,当然,运气还好,MySQL没有直接crash,或者启动不起来。多谢donny的招财猫保佑阿。但是这个问题的严重性可想而知。由于短时间内没有什么特别好的办法让MySQL自己再启动binlog的日志写,并且单点的情况不能这样一直持续下去。所以我们只好联系cobar和应用人员,申请停机,并重搭备机和复制。朝阳帮忙联系中文站的,我联系itbu的。中文站的offer重要性从朝阳打的电话数就可以看出来…….这回真是搞大了。

我和朝阳最终确定了offer重启的步骤。并文档化下来,以预先计划好重启过程中步骤和可能出现的问题。

pm同样的有这个问题,我们10点半的时候尝试了一下修复pm的一套同样出问题的机器。结果发现了问题的根源所在。chmod 750 /data这个东西导致了数据库不能访问对应的文件夹。/data这个目录是root创建的,它的属主和属组为root:root。当mysql尝试进入这个目录的时候,它是作为“other”进去的,而other的权限是0。所以无法访问/data目录,就算/data里面的mysqldata是mysql:mysql的。最终我们整理好了最终修改目录权限的命令组。修改以后重启MySQL;MySQL正在运行的时候修改目录属性两种方式下,MySQL都不会出现问题了。

另外,我们还尝试几种办法,想让MySQL自己把记录binlog的方法启动起来,结果都失败了:
1、flush logs; 命令执行成功,但是MySQL没有启动log_bin
2、reset master;
执行报错:
root@localhost : (none) 10:24:09> reset master;
ERROR 1186 (HY000): Binlog closed, cannot RESET MASTER

最终还是不得不在凌晨0点和2点分别对offer和pm应用的MySQL服务器重启了MySQL主备机。重启的过程比较顺利,基本在3-5分钟之内,应用就重新连过来了。

这次的出问题的教训和以后需要注意的问题:
1、修改文件,目录的权限,属组,属主的时候需要特别注意,特别是正在提供应用服务的进程需要访问的目录。
2、当需要大规模部署的时候,需要充分测试。我这边虽然在pm-my1b MySQL机器上测试过了,但是由于它是备机,写压力和对文件的访问没有那么频繁,所以短时间内也看不出什么问题。如果在一台主机上在测试一下,也许就会发现问题。
3、拜DBA团队的保护神“招财猫”的时候,要心诚。不能心里还在挂着苍井空,饭岛爱。

附一、offer两台出问题的主机MySQL重启详细步骤如下:
目前主要是两套机器需要停止重启。第4套和第8套。
先停止第四套,然后操作第八套。
停止第4套的操作如下:
1、11点,设置主机(xy-offer-db4b    172.22.32.40)的dirty page为0;因为备机没有数据应用,所以备机不用设置这个。
set global innodb_max_dirty_pages_pct = 0;
2、检查主机(xy-offer-db4b    172.22.32.40)dirty page数是否一直在减少。
mysqladmin -uroot  ext -i3 | grep dirty
3、11点30分,停止备机(xy-offer-db4c    172.22.32.56 )的mysql
service mysql stop
4、12点。主机(xy-offer-db4b    172.22.32.40)flush tables将数据刷入磁盘:
登录mysql: flush tables。
5、刷新成功后,停止和重启主机(xy-offer-db4b    172.22.32.40)的mysql
service mysql stop
service mysql start
6、检查主机(xy-offer-db4b    172.22.32.40)mysql启动是否有错误。有错误及时修复。
正常的文件夹权限为:
drwxr-xr-x   5 root root  4096 May 25 22:13 data
drwxr-xr-x 3 mysql mysql  4096 Dec 25  2008 mysqldata
drwxr-xr-x 2 mysql mysql 4096 May 24 15:15 binlog
drwxr-xr-x 2 mysql mysql 4096 Feb 24 15:13 innodb_log
drwxr-xr-x 2 mysql mysql 4096 Feb 24 15:13 innodb_ts
drwxr-xr-x 2 mysql mysql 4096 Feb 24 15:13 relaylog
drwxr-xr-x 7 mysql mysql 4096 May 24 08:05 mydata
drwxr-xr-x 2 mysql mysql 4096 May 25 11:08 tmpdir
drwxr-xr-x  26 root root  4096 May 25 22:16 var
drwxr-xr-x 26 root root  4096 Feb 26 14:58 lib
drwxr-xr-x 12 mysql mysql 4096 Feb 24 11:07 mysql
drwxr-xr-x  2 mysql mysql 4096 Mar  4 17:12 sock
drwxr-xr-x  2 mysql mysql 4096 Apr 28 09:51 log
7、检查cobar连接是否有问题。
8、检查应用连接是否有问题。
然后检查第8套。
1、11点,设置主机(xy-offer-db8c    172.22.32.60)的dirty page为0;因为备机没有数据应用,所以备机不用设置这个。
set global innodb_max_dirty_pages_pct = 0;
2、检查主机(xy-offer-db8c    172.22.32.60)dirty page数是否一直在减少。
mysqladmin -uroot  ext -i3 | grep dirty
3、11点30分,停止备机(xy-offer-db8b    172.22.32.44 )的mysql
service mysql stop
4、12点。主机(xy-offer-db8c    172.22.32.60)flush tables将数据刷入磁盘:
登录mysql: flush tables。
5、刷新成功后,停止和重启主机(xy-offer-db8c    172.22.32.60)的mysql
service mysql stop
service mysql start
6、检查主机(xy-offer-db8c    172.22.32.60)mysql启动是否有错误。有错误及时修复。
正常的文件夹权限为:
drwxr-xr-x   5 root root  4096 May 25 22:13 data
drwxr-xr-x 3 mysql mysql  4096 Dec 25  2008 mysqldata
drwxr-xr-x 2 mysql mysql 4096 May 24 15:15 binlog
drwxr-xr-x 2 mysql mysql 4096 Feb 24 15:13 innodb_log
drwxr-xr-x 2 mysql mysql 4096 Feb 24 15:13 innodb_ts
drwxr-xr-x 2 mysql mysql 4096 Feb 24 15:13 relaylog
drwxr-xr-x 7 mysql mysql 4096 May 24 08:05 mydata
drwxr-xr-x 2 mysql mysql 4096 May 25 11:08 tmpdir
drwxr-xr-x  26 root root  4096 May 25 22:16 var
drwxr-xr-x 26 root root  4096 Feb 26 14:58 lib
drwxr-xr-x 12 mysql mysql 4096 Feb 24 11:07 mysql
drwxr-xr-x  2 mysql mysql 4096 Mar  4 17:12 sock
drwxr-xr-x  2 mysql mysql 4096 Apr 28 09:51 log
7、检查cobar连接是否有问题。
8、检查应用连接是否有问题。

主机检查没有问题。就可以搭备机了,

附二、最终修改文件目录属性的命令组

chown -R mysql:mysql /var/lib/mysql
chmod -R 770 /var/lib/mysql
chmod -R 770 /var/lib/mysql/etc
chmod -R 770 /var/lib/mysql/log
chmod -R 770 /var/lib/mysql/sock

chmod -R 755 /data

chown -R mysql:mysql /data/mysqldata
chmod -R 755 /data/mysqldata
chmod -R 700 /data/mysqldata/mydata
chmod -R 700 /data/mysqldata/innodb_log
chmod -R 700 /data/mysqldata/innodb_ts
chmod -R 750 /data/mysqldata/binlog
chmod -R 750 /data/mysqldata/relaylog
chmod -R 750 /data/mysqldata/tmpdir

research report for MySQL multi-master tool

把老的多主master向同一个slave复制的文档找出来了。这个是研究性质的,不会牵涉到太多技术细节,所以不担心会有泄漏以前公司技术的嫌疑。之前公司的这个软件已经完成了,包括事件解析,应用以及冲突处理。我自己想做一个开源的,multi_master的版本,但是架构基本上会非常不同。希望有时间能够真正的做出来阿,这个功能相信对我们还是非常有用的。

Multi-Master

测试报告

目 录

目 录 2

1 报告信息 3

2 概述 4

2.1 目的 4

2.2 相关信息 4

2.3 MySQL replication数据复制格式 5

3 测试过程 9

3.1 测试进度安排 9

3.2 测试过程描述 9

4 测试结论 12

4.1 测试最终结论 12

5 附录 13

5.1 附录1 MySQL 5.1.20 Beta包含的事件类型 13

5.2 附录2 MySQL 5.1.20 Beta各事件的附加事件头长度 14

5.3 附录3 MySQL 5.1.20 Beta中各列在内部存储时可能的各种数据类型 16

5.4 附录4 MySQL 5.1.20 Beta中各ROW_EVENT的m_flags包含的标志位 17

概述

目的

测 试是否可以模拟MySQL replication的功能构建一个小工具。该工具模拟replication端的两个线程:I/O线程和SQL线程。I/O线程用于从master端 (主服务器)注册自己和申请获得master的binlog信息,把获得的binlog信息进行解析并保存在本地relay-log中。SQL线程负责读 取解析relay-log并在本地MySQL服务器上执行这些语句。从而达到从master端复制数据并在slave端(从服务器)执行,以及时同步 slave的目的。用这个工具,我们可以避免MySQL只能从单个master中同步数据的限制,实现从多个master中复制数据,同步slave的功 能。

相关信息

项目来源

在我们GSB的数据库复制同步中,杭州 的GSB数据库需要从不同的master端获得数据,并同步到自己的数据库中。而MySQL本身不提供multi-master的功能,只能从单个 master中复制和同步数据,并且在将来一段时间也不准备增加这个功能。所以我们打算先对MySQL的源代码进行理解和分析,并尝试着模拟MySQL replication的功能,研究multi-master工具的可行性。

测试环境

在 这次测试中我们主要用到了两台机器,192.168.1.112和192.168.1.113,它们都是虚拟机,安转的操作系统为Linux 2.6.16。其中112机器中安装的MySQL server版本为:5.0.27-standard-log,作为master。113机器安装的MySQL server版本为:5.0.24-max-log,作为slave。由于采用了增加server_id列标识提交语句的site的策略,我们将测试环境 中的两个MySQL版本升级为5.1.20 Beta。

其他相关信息

MySQL的 数据复制功能主要涉及到三个线程(master端一个,slave端两个)。当用户在slave端提交start slave;slave端将首先创建I/O线程,I/O线程会连接到master并请求master将其binlog中的语句发送回来。Master接收 到请求后将创建一个线程(Binlog Dump)用于发送binlog信息给slave,用户可以通过Show processlist在master端看到此线程。I/O线程读取主服务器Binlog Dump线程发送的内容并将该数据拷贝到从服务器数据目录中的本地文件中,即中继日志(relay-log。第3个线程是SQL线程,是slave端创建的用于读取中继日志并执行日志中语句的线程。

如果有多个从服务器,主服务器将为每个当前连接的从服务器创建一个(Binlog Dump)线程;每个从服务器都有自己的I/O和SQL线程。

MySQL复制基于服务器在二进制日志中对所有数据库的更改(更新、删除等等)。因此,要进行复制,必须在主服务器上启用二进制日志。

从MySQL 5.1开始,MySQL replication可以支持两种复制类型,第一种是原有的statement based replication,也就是将在master端提交的查询语句及相关环境一起记录到binlog中,slave模拟该环境并提交语句执行。第二种是 row based replication,即将master端提交的查询语句改变的所有行数据记录到binlog中,slave端获得这些数据直接提交给MySQL server,修改对应数据文件。这两种复制类型可以单独运行或者混合起来。MySQL默认的复制类型就是混合类型的复制,它根据查询类型,表的类型等相 关因素确定该表的复制类型。

在每一个数据库的表中,我们增加了一列server_id用于模拟线程变量 thd->server_id。但是由于server_id存在于表中并且将随表数据一同复制,在复制中不能随意改变,所以这个方法存在一定的限 制,后面我们将详细描述。由于我们使用server_id列判断表中某一行(row)数据操作的最先发起site,所以我们必须保证复制的列中包括这一 列,这样我们必须限制MySQL master和slave端replication的类型为row based,即row-based replication。注意:row-based replication是MySQL 5.1版本才被引入的。下面我们所涉及的复制除了特殊指明外都是指row based replication。

在MySQL 5.1.20 Beta版本中,在master端如果一条语句改变了一个表的多条数据,master将首先在binlog中记录下一个Tablemap事件用于将表的相 关信息以及表的id信息对应记录下来。而针对每一行需要改变的数据,master端单独记录一个 writerow(updaterow,deleterow)事件,而且每一行数据都是二进制的MySQL内部格式的数据。其中writerow对应的查 询语句是:insert和replace;updaterow对应的是update语句;deleterow对应的是delete语句。而 writerow事件中除了环境数据以外还包含了对应的行数据为record[0]。record[0]中包含有将要插入MySQL表中的一行数据。 Update语句包含两个行数据,record[0]表示将要更新存储到表中的行数据,而record[1]中包含了update将要替换的已存在于表中 的行数据。Delete的record[0]中包含的是将要删除的行数据。由于binlog中的数据都是二进制的MySQL格式的数据,而我们也没有找到 直接将这些数据插入MySQL server的接口,所以在对这些数据操作之前,我们首先必须将这些数据转换回可读数据。然后就可以直接向MySQL server提交对应的查询语句。

每种不同的类型在MySQL中保存的格式都不一样,MySQL 5.1.20 Beta的数据类型见附录3。

MySQL replication数据复制格式

这 里我们基于MySQL 5.1.20 Beta描述MySQL两个slave端的thread发送和接收数据的格式。某些字段所占的字节数跟MySQL的版本有关,这里我们所描述的为 binlog版本为4,MySQL server版本为5.1.20 Beta下的数据格式。

MySQL I/O thread数据格式

向主服务器注册自己

向 主服务器注册自己并不是一个必须的操作,如果没有注册同样可以向主服务器请求数据。如果需要向主服务器注册,那么可以调用mysql.h中的 simple_command(mysql, command, arg, length, skip_check)函数,在arg参数中依序填入下述的各个字段,并指定其中的参数command为COM_REGISTER_SLAVE以注册自 己。

名称 字节数 含义
server_id 4 本MySQL instance的server_id值
strlen(report_host) 1 or 2 标识接下来的report_host的长度,如果长度<251占1个字节,否则占两个字节
report_host Strlen(report_host) 向主服务器注册的MySQL instance标识
strlen(report_user) 1 or 2 标识接下来的report_user的长度,如果长度<251占1个字节,否则占2个字节
report_user Strlen(report_user) 向主服务器注册的用户名
strlen(report_password) 1 or 2 标识接下来的report_password的长度,如果长度<251占1个字节,否则占2个字节
report_password Strlen(report_password) 向主服务器注册的密码
report_port 2 向主服务器注册的端口
rpl_recovery_rank 4 复制的恢复等级
master_id 4 填入0,主服务器将自行填入master_id值
register_master

register_master

图1、主服务器注册示意图

向主服务器请求数据

从 服务器向主服务器发送了请求数据的命令以后主服务器将根据要求将对应binlog文件的指定位置开始的事件记录发送给从服务器。向主服务器请求数据,可以 调用mysql.h中的simple_command(mysql, command, arg, length, skip_check)函数,在arg参数中依序填入下述的各个字段,并指定其中的参数command为COM_BINLOG_DUMP。

名称 字节数 含义
master_log_pos 4 请求主服务器发送的事件记录在binlog文件中的偏移量
binlog_flags 2 暂时填0,做扩展用
server_id 4 本MySQL instance的server_id值
logname Strlen(logname) 请求主服务器发送的binlog文件的文件名

如果没有指定MySQL使用methods,那么我们应该调用函数sql_common.h头文件中的cli_advanced_command()代替simple_command()。

向 主服务器请求了数据以后,从服务器就可以通过cli_safe_read(mysql);获得主服务器发送过来的数据,每次获得一个事件记录的数据。 cli_safe_read的返回值标示了从主服务器发送过来的数据的数据字节数。而发送过来的数据保存在 mysql->net->read_pos数组中。I/O thread模块可以利用MySQL的io_cache将对应事件记录存储到relay-log文件中。

MySQL binlog文件初始化

由于MySQL binlog的特殊性,以及为了mysqlbinlog工具能够识别我们relay-log文件,在新建一个relay-log文件时必须写入一定的初始化数据。这些初始化数据依序包括如下字段:

名称 字节数 含义
BINLOG_MAGIC(即”\xfe\x62\x69\x6e”) BIN_LOG_HEADER_SIZE(4) Binlog文件的标识值

MySQL SQL thread数据格式

只 要循环的调用cli_safe_read函数,从服务器可以不断得到从主服务器发送过来的事件记录。接下来我们介绍一下相关的一些事件记录格式。在提交了 COM_BINLOG_DUMP命令后,主服务器首先给从服务器发送的两个事件依序分别为ROTATE_EVENT和 FORMAT_DESCRIPTION_EVENT事件。ROTATE_EVENT事件用来标示接下来主服务器将从哪一个binlog文件的哪个位置开始 发送事件记录。而FORMAT_DESCRIPTION_EVENT事件用来记录本MySQL instance的server_id值,binlog版本号,MySQL server的版本,本relay-log创建的时间以及各个不同事件的事件头所占的字节数等信息。我们关心的其他的事件记录的格式包括 WRITE_ROWS_EVENT,UPDATE_ROWS_EVENT,DELETE_ROWS_EVENT等。

事件头字段描述

各个事件都包括一个事件头,事件头的字段格式如下:

名称 字节数 含义
When 4 事件的创建时间。
Type 1 事件的类型(见附录1)。
server_id 4 事件发生时所在MySQL的server_id值。
data_written 4 该事件一共占用的字节数,包括事件头的字节数。
log_pos 4 下一事件在binlog文件中将要开始的位置,即本事件的结束位置
Flags 2 事件的其他标志位。

ROTATE_EVENT事件字段描述

由于各个事件的事件头基本一致,这里我们就不重复介绍事件头的各字段了,后面的各个事件我们也都将忽略对事件头字段的描述。

ROTATE_EVENT事件的附加事件头字段主要包括:

名称 字节数 含义
pos 8 主服务器将要发送的事件记录在binlog文件中的偏移量。一般为从服务器提交的COM_BINLOG_DUMP请求中的偏移量值。

ROTATE_EVENT事件的其他信息字段主要包括:

名称 字节数 含义
new_log_ident strlen(new_log_ident) 主服务器将要发送的事件记录的binlog文件名。一般为从服务器提交的COM_BINLOG_DUMP请求中的binlog文件名。

FORMAT_DESCRIPTION_EVENT事件字段描述

FORMAT_DESCRIPTION_EVENT事件的附加事件头的字段如下:

名称 字节数 含义
binlog_version 2 Binlog文件的版本号,这里一般为最新的版本号4
server_version ST_SERVER_VER_LEN(50) MySQL的版本号。例如:” 5.1.20-beta-log”
Created 4 事件创建时间,这里一般和事件头中的when一致
event_header_len 1 一般事件的事件头长度,一般设置为:LOG_EVENT_HEADER_LEN(19)
post_header_len ENUM_END_EVENT-1(26) 不同事件类型的附加事件头的长度,见附录2。

TABLE_MAP_EVENT事件字段描述

TABLE_MAP_EVENT事件的附加事件头的字段如下:

名称 字节数 含义
m_table_id 6(5.1.4前的版本中为4) 表的id标识符
m_flags 2 表的各种标志位,见附录4

TABLE_MAP_EVENT事件的其他信息字段主要包括:

名称 字节数 含义
m_dblen 1 数据库名的长度
m_dbnam m_dblen+1 数据库名,以’\0’结尾
m_tbllen 1 表名的长度
m_tblnam m_tbllen+1 表名,以’\0’结尾
m_colcnt net_field_length() 表的字段个数,所占字节数根据第一个字节的大小由net_field_length函数确定
m_coltype m_colcnt 表的各个字段的字段类型,参见附录3。

WRITE_ROWS_EVENT事件字段描述

WRITE_ROWS_EVENT事件的附加事件头的字段如下:

名称 字节数 含义
m_table_id 6(5.1.4前的版本中为4) 表的id标识符
m_flags 2 表的各种标志位,见附录4

WRITE_ROWS_EVENT事件的其他信息字段主要包括:

名称 字节数 含义
m_width net_field_length() 表的各列的位图长度,所占字节数根据第一个字节的大小由net_field_length函数确定
m_cols.bitmap (m_width + 7) / 8 表的各列的位图,每一位表示m_rows_buf是否包含表中一列的值,如果没有置位表示该列的值没有包含在m_rows_buf中
m_rows_buf 剩余字节数(len-已占字节数) 将要插入到表中的一行数据值。

UPDATE_ROWS_EVENT事件字段描述

UPDATE_ROWS_EVENT事件的附加事件头的字段如下:

名称 字节数 含义
m_table_id 6(5.1.4前的版本中为4) 表的id标识符
m_flags 2 表的各种标志位,见附录4

UPDATE_ROWS_EVENT事件的其他信息字段主要包括:

名称 字节数 含义
m_width net_field_length() 表的各列的位图长度,所占字节数根据第一个字节的大小由net_field_length函数确定
m_cols.bitmap (m_width + 7) / 8 表中被匹配行数据的各列的位图,每一位表示m_rows_buf是否包含表中该列的值。
m_cols_ai.bitmap (m_width + 7) / 8 表中将要更新的行数据的各列的位图,每一位表示m_rows_buf是否包含表中一列的值。
m_rows_buf 剩余字节数(len-已占字节数) 表中被匹配的那一行数据的值以及将要更新的一行数据值。

DELETE_ROWS_EVENT事件字段描述

DELETE_ROWS_EVENT事件的附加事件头的字段如下:

名称 字节数 含义
m_table_id 6(5.1.4前的版本中为4) 表的id标识符
m_flags 2 表的各种标志位

DELETE _ROWS_EVENT事件的其他信息字段主要包括:

名称 字节数 含义
m_width net_field_length() 表的各列的位图长度,所占字节数根据第一个字节的大小由net_field_length函数确定
m_cols.bitmap (m_width + 7) / 8 表的各列的位图,每一位表示m_rows_buf是否包含表中一列的值。
m_rows_buf 剩余字节数(len-已占字节数) 表中将要删除的一行数据值。

XID_EVENT事件字段描述

XID_EVENT一般出现在一个事务操作(transaction)之后或者其他语句提交之后。它的主要作用是提交事务操作和把事件刷新至binlog文件中。

XID_EVENT事件的信息字段包括:

名称 字节数 含义
xid sizeof(xid) 8 commit标识符

测试过程

测试进度安排

2007-07-26 —— 2007-08-03

理解和分析MySQL slave端I/O线程以及SQL线程的实现细节。

2007-08-04 —— 2007-08-08

模拟实现MySQL replication的I/O线程,实现向master请求binlog,并记录读到的信息到一个本地日志文件relay-log中的功能。使用mysqlbinlog应该能查看该日志。

2007-08-09 —— 2007-08-15

模拟实现MySQL replication的SQL线程,实现读取并解析relay-log文件,设置slave端的执行环境以提交查询。

2007-09-03 —— 2007-09-07

熟悉并了解MySQL 5.1.20 Beta 中row based replication配置以及实现。

2007-09-10 —— 2007-09-21

模拟实现MySQL 5.1.20 Beta中insert,update,delete等语句的在slave段的解析并生成对应的语句。

2007-09-24 —— 2007-09-30

阅读和了解replication 冲突解决方案,分析update(delete)更新0 row冲突和update(delete)复制执行空语句之间的区别。

测试过程描述

通过对MySQL源代码的阅读,我们了解并熟悉了MySQL replication的基本原理和实现细节。

对MySQL I/O线程的模拟相对比较简单,这个线程的向master注册自己及请求发送binlog信息都有相应的接口提供出来,并且对于从master端接收的信 息也只做了比较简单的分析就直接将该信息存入relay-log日志文件中。所以我们模拟I/O线程比较顺利。

对于MySQL的SQL线程的模拟实现中,读取和解析relay-log日志文件虽然比较繁琐,但是基本实现的困难不大。主要的困难出现在如何设置slave端的执行环境。

这 里设置slave端的执行环境包括从master端复制过来的一些环境信息,比如:server_id,charset,timezone, auto_increment_increment,auto_increment_offset等等。其中最重要的是server_id,它表示 master发送过来的binlog信息中,将要执行的这一条语句最开始是在哪一个MySQL server中提交执行的。如果配置了环形的replication链,并且复制过来的server_id与本机的server_id相等的话,那么说明 这条语句最开始就是在本机中执行的,它对应的语句应该被忽略。

如果从master服务器复制过来的server_id与本机的 server_id不同,那么就应该在本机执行这条语句。这里我们特别要注意的是:在记录本地binlog日志文件时,server_id应该保证为复制 过来的server_id,而不是我们普通提交查询时记录的本地的server_id。不然在配置双向复制和环形复制链时将造成数据复制的死循环,从而造 成数据紊乱。

我们仔细察看了SQL线程的源代码,发现它在读取relay-log记录时会把复制过来的server_id信息保存在对应 的SQL线程变量thd->server_id中,从而在写本机binlog日志文件时记录的server_id将会是从master端复制过来的 server_id信息。如果我们要模拟SQL线程,我们需要在提交查询前修改连接会话的相应变量。但由于server_id在MySQL中是一个全局变 量,而不是一个会话期变量,所以我们不能在连接中修改这个变量(不然,从修改了server_id后到恢复本机的server_id值之间的这一段时间 里,在binlog日志文件中记录的用户提交的语句对应的server_id值将保持为修改后的值,而不是本机server_id值)。在写binlog 日志时,写入的server_id值依赖于连接线程中的server_id值,而MySQL server也没有提供任何修改连接线程中对应的server_id变量值的接口。这样,我们无法模拟SQL线程来执行复制过来的语句。

为 了能够标识数据最新的插入和更新site,我们在每一个数据库表上都增加了一列server_id。在对表执行insert操作时,server_id将 自动赋值为该site的server_id值。也就是说,在增加server_id列时设置它的default值为该MySQL的server_id值。 而为了保证在本机执行的update操作对server_id有同样的影响,我们可以借助MySQL的trigger功能,使得在本机上执行的 update操作修改行数据值中server_id的值为本机的server_id值。另外,我们还要注意的是用户在提交查询时不应该自己操作 server_id值,而应该通过我们设置的MySQL的已有机制进行操作,以防出现数据复制的死循环。下面我们分别描述 insert,update,delete操作的提交和复制过程。

  1. Insert的提交和复制:

由 于在MySQL的row based replication中insert和replace语句在binlog中都被记录为write row事件,所以我们把replace语句的提交和复制合并到insert的提交和复制中。Insert的提交和复制相对来说比其他的的操作简单。我们只 要设置server_id列的default值为对应MySQL server的server_id值,当用户提交insert查询时,server_id将自动赋值。而MySQL将把insert插入的每一行数据自动 地对应一个write row事件并记录在binlog中。其他的slave可以通过我们的工具将binlog文件复制到本地,然后解析write row以生成对应的replace语句。通过server_id列的值我们可以很容易的知道最开始提交insert语句的MySQL的server_id 值,从而在该write row事件复制到该MySQL instance时停止复制,而避免数据复制的死循环。

  1. Update的提交和复制:

Update 的提交和复制比较复杂。在一个MySQL上提交的update语句将被对应为一个update row事件记录在binlog文件中,slave端复制并解析update row事件生成对应的update语句。这里我们举一个数据复制的例子:如果某一条数据首先在MySQL instance 1(M1)上插入,那么该数据的server_id列值为1,该数据复制到MySQL instance 2(M2)数据将保持不变。但是,如果此时在M2上提交的update语句更新了该行数据,那么server_id值仍然保持为1。如果该update row事件重新复制回M1,那么我们的复制工具发现该行数据的server_id值与本MySQL instance的server_id值一样,认为该update语句最开始就是在本机提交执行的,它将忽略该update语句。针对这个问题,我们有两 种解决方案:

      1. 由于在MySQL server中,update要更新的那一行数据匹配不成功那么对于MySQL数据库的将不会有任何修改,并且这一条update语句也将不会记录到 binlog文件中。我们可以利用这一点允许multi-master工具在解析update row事件时忽略对server_id的检查,允许从其他MySQL instance复制的在本MySQL instance提交的update语句继续执行,实际上该语句将由于匹配不到对应的数据而执行空操作。这里执行的空操作实际上和我们后面要讨论的一种冲 突类型(slave端提交的查询不能更新数据冲突)是一样的。

这里我们仍然用上面的例子进行说明:

首 先在M1上提交的insert语句被复制到M2并插入对应的数据,而当用户更新这一行数据后,update row事件复制回M1时,由于multi-master工具不再检查server_id值,那么同样的update语句将在M1执行。M2重新复制得到 update row事件并生成对应update语句,但是由于该update语句是最先在M2提交成功的,那么正常情况下该update语句不能匹配到要修改的那一行 数据,从而执行一条空语句,不会出现数据复制的死循环。当然,如果在复制回M2数据前有其他的语句insert或者update生成了若干行能够被匹配的 数据的话,这种方案是行不通的。

      1. 第二种解决方案相对复杂一些。它保证了 update语句更新行数据的server_id值为本MySQL server的server_id值。我们利用了MySQL提供的trigger,在update语句提交执行之前改变行数据NEW的server_id 值为本MySQL server的server_id值。(在MySQL的trigger中,OLD行数据表示数据库中已有的将要被update匹配的数据,而NEW行数据 表示update语句将要更新为其值的行数据)。但是,由于我们的multi-master工具将生成的语句直接提交到本MySQL instance中执行,那么如果我们的trigger不能识别工具提交的语句和用户提交的语句,而把NEW行数据的server_id值全部改成本 instance的值,复制的死循环一定会出现。例如:在上面的例子中,M1的server_id为1,在M2上update的数据复制到M1上提交执 行,如果trigger不能识别出它是从multi-master工具生成的语句,而是把它的NEW行数据修改为1,那么记录在M1的binlog中的数 据将不能标识最开始提交语句的MySQL instance位置,从而不能中断数据复制的循环。至少有两种方法可以区分普通的用户提交语句和multi-master工具提交的语句。1,专门指定 一个用户用于multi-master工具提交查询语句,以区别于其他用户提交的语句。在trigger中我们可以用user()函数获得用户名来确定查 询语句提交的来源。2,通过multi-master修改相应语句而且使得它提交的所有语句与普通用户提交的语句不同。下面我们详细阐述第二种策略。

我 们发现用户提交的所有update语句都有一个共同的特点:OLD行数据和NEW行数据的server_id值相等。如果我们在用工具生成OLD行数据和 NEW行数据的server_id值相等的update语句时,将NEW行数据的server_id值改变(例如改为0)以区别于用户提交的语句。这样 trigger就能根据是否用户提交的语句而进行相应的操作了。一般来说,我们的trigger可以写成如下的形式:

delimiter //

create trigger update_set_srvid_test_test001 before update on test001 for each row

BEGIN

IF NEW.server_id=OLD.server_id THEN

SET NEW.server_id=1;

ELSEIF NEW.server_id=0 THEN

SET NEW.server_id=OLD.server_id;

END IF;

END;//

delimiter ;

  1. Delete的提交和复制:

Delete 的提交和复制和update的基本类似。在一个MySQL上提交的delete语句将被对应为一个delete row事件记录在binlog文件中,slave端复制文件并解析delete row事件生成对应的delete语句提交执行。为了解决本MySQL上insert的数据在别的MySQL instance上delete的问题,我们的第一个解决方案和update一样,也是忽略对server_id值的检查。同样,它存在着两个缺点:1. 它与冲突解决方案中的一种类型是一样的,我们不能区别这样的一个空操作是否是一个冲突。2. 如果在delete语句复制回本instance前,有其他的语句产生了该delete语句能够匹配的行数据,那么这一行数据将被错误的删除掉。同时,因 为delete不包括NEW行数据,update的第二个解决方案对delete语句不适用。

测试结论

测试最终结论

通过上面的测试和分析,我们发现可以通过增加server_id列和增加trigger等手段模拟MySQL的replication功能,从而实现MySQL的多主复制工具multi-master。

附录

附录1 MySQL 5.1.20 Beta包含的事件类型

下面列举了各种MySQL的事件类型(代码拷贝自MySQL 5.1.20的源代码):

enum Log_event_type

{

/*

Every time you update this enum (when you add a type), you have to

fix Format_description_log_event::Format_description_log_event().

*/

UNKNOWN_EVENT= 0,

START_EVENT_V3= 1,

QUERY_EVENT= 2,

STOP_EVENT= 3,

ROTATE_EVENT= 4,

INTVAR_EVENT= 5,

LOAD_EVENT= 6,

SLAVE_EVENT= 7,

CREATE_FILE_EVENT= 8,

APPEND_BLOCK_EVENT= 9,

EXEC_LOAD_EVENT= 10,

DELETE_FILE_EVENT= 11,

/*

NEW_LOAD_EVENT is like LOAD_EVENT except that it has a longer

sql_ex, allowing multibyte TERMINATED BY etc; both types share the

same class (Load_log_event)

*/

NEW_LOAD_EVENT= 12,

RAND_EVENT= 13,

USER_VAR_EVENT= 14,

FORMAT_DESCRIPTION_EVENT= 15,

XID_EVENT= 16,

BEGIN_LOAD_QUERY_EVENT= 17,

EXECUTE_LOAD_QUERY_EVENT= 18,

TABLE_MAP_EVENT = 19,

/*

These event numbers were used for 5.1.0 to 5.1.15 and are

therefore obsolete.

*/

PRE_GA_WRITE_ROWS_EVENT = 20,

PRE_GA_UPDATE_ROWS_EVENT = 21,

PRE_GA_DELETE_ROWS_EVENT = 22,

/*

These event numbers are used from 5.1.16 and forward

*/

WRITE_ROWS_EVENT = 23,

UPDATE_ROWS_EVENT = 24,

DELETE_ROWS_EVENT = 25,

/*

Something out of the ordinary happened on the master

*/

INCIDENT_EVENT= 26,

/*

Add new events here – right above this comment!

Existing events (except ENUM_END_EVENT) should never change their numbers

*/

ENUM_END_EVENT /* end marker */

};

附录2 MySQL 5.1.20 Beta各事件的附加事件头长度

下面列举了MySQL 5.1.20 Beta各事件的附加事件头长度(拷贝自MySQL源代码):

/* event-specific post-header sizes */

// where 3.23, 4.x and 5.0 agree

#define QUERY_HEADER_MINIMAL_LEN (4 + 4 + 1 + 2)

// where 5.0 differs: 2 for len of N-bytes vars.

#define QUERY_HEADER_LEN (QUERY_HEADER_MINIMAL_LEN + 2)

#define LOAD_HEADER_LEN (4 + 4 + 4 + 1 +1 + 4)

#define START_V3_HEADER_LEN (2 + ST_SERVER_VER_LEN + 4)

#define ROTATE_HEADER_LEN 8 // this is FROZEN (the Rotate post-header is frozen)

#define CREATE_FILE_HEADER_LEN 4

#define APPEND_BLOCK_HEADER_LEN 4

#define EXEC_LOAD_HEADER_LEN 4

#define DELETE_FILE_HEADER_LEN 4

#define FORMAT_DESCRIPTION_HEADER_LEN (START_V3_HEADER_LEN+1+LOG_EVENT_TYPES)

#define ROWS_HEADER_LEN 8

#define TABLE_MAP_HEADER_LEN 8

#define EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN (4 + 4 + 4 + 1)

#define EXECUTE_LOAD_QUERY_HEADER_LEN (QUERY_HEADER_LEN + EXECUTE_LOAD_QUERY_EXTRA_HEADER_LEN)

#define INCIDENT_HEADER_LEN 2

post_header_len[START_EVENT_V3-1]= START_V3_HEADER_LEN;

post_header_len[QUERY_EVENT-1]= QUERY_HEADER_LEN;

post_header_len[ROTATE_EVENT-1]= ROTATE_HEADER_LEN;

post_header_len[LOAD_EVENT-1]= LOAD_HEADER_LEN;

post_header_len[CREATE_FILE_EVENT-1]= CREATE_FILE_HEADER_LEN;

post_header_len[APPEND_BLOCK_EVENT-1]= APPEND_BLOCK_HEADER_LEN;

post_header_len[EXEC_LOAD_EVENT-1]= EXEC_LOAD_HEADER_LEN;

post_header_len[DELETE_FILE_EVENT-1]= DELETE_FILE_HEADER_LEN;

post_header_len[NEW_LOAD_EVENT-1]= post_header_len[LOAD_EVENT-1];

post_header_len[FORMAT_DESCRIPTION_EVENT-1]= FORMAT_DESCRIPTION_HEADER_LEN;

post_header_len[TABLE_MAP_EVENT-1]= TABLE_MAP_HEADER_LEN;

post_header_len[WRITE_ROWS_EVENT-1]= ROWS_HEADER_LEN;

post_header_len[UPDATE_ROWS_EVENT-1]= ROWS_HEADER_LEN;

post_header_len[DELETE_ROWS_EVENT-1]= ROWS_HEADER_LEN;

/*

We here have the possibility to simulate a master of before we changed

the table map id to be stored in 6 bytes: when it was stored in 4

bytes (=> post_header_len was 6). This is used to test backward

compatibility.

This code can be removed after a few months (today is Dec 21st 2005),

when we know that the 4-byte masters are not deployed anymore (check

with Tomas Ulin first!), and the accompanying test (rpl_row_4_bytes)

too.

*/

DBUG_EXECUTE_IF(“old_row_based_repl_4_byte_map_id_master”,

post_header_len[TABLE_MAP_EVENT-1]=

post_header_len[WRITE_ROWS_EVENT-1]=

post_header_len[UPDATE_ROWS_EVENT-1]=

post_header_len[DELETE_ROWS_EVENT-1]= 6;);

post_header_len[BEGIN_LOAD_QUERY_EVENT-1]= post_header_len[APPEND_BLOCK_EVENT-1];

post_header_len[EXECUTE_LOAD_QUERY_EVENT-1]= EXECUTE_LOAD_QUERY_HEADER_LEN;

post_header_len[INCIDENT_EVENT-1]= INCIDENT_HEADER_LEN;

附录3 MySQL 5.1.20 Beta中各列在内部存储时可能的各种数据类型

enum enum_field_types

{

MYSQL_TYPE_DECIMAL = 0,

MYSQL_TYPE_TINY = 1,

MYSQL_TYPE_SHORT = 2,

MYSQL_TYPE_LONG = 3,

MYSQL_TYPE_FLOAT = 4,

MYSQL_TYPE_DOUBLE = 5,

MYSQL_TYPE_NULL = 6,

MYSQL_TYPE_TIMESTAMP = 7, // 4 from_unixtime(0x)

MYSQL_TYPE_LONGLONG = 8,

MYSQL_TYPE_INT24 = 9, //field_medium

MYSQL_TYPE_DATE = 10,

MYSQL_TYPE_TIME = 11,

MYSQL_TYPE_DATETIME = 12,

MYSQL_TYPE_YEAR = 13,

MYSQL_TYPE_NEWDATE = 14,

MYSQL_TYPE_VARCHAR = 15, //field_varstring

MYSQL_TYPE_BIT = 16,

MYSQL_TYPE_NEWDECIMAL = 246,

MYSQL_TYPE_ENUM = 247,

MYSQL_TYPE_SET = 248,

MYSQL_TYPE_TINY_BLOB = 249,

MYSQL_TYPE_MEDIUM_BLOB = 250,

MYSQL_TYPE_LONG_BLOB = 251,

MYSQL_TYPE_BLOB = 252,

MYSQL_TYPE_VAR_STRING = 253,

MYSQL_TYPE_STRING = 254,

MYSQL_TYPE_GEOMETRY = 255,

};

附录4 MySQL 5.1.20 Beta中各ROW_EVENT的m_flags包含的标志位

在MySQL 5.1.20 Beta中,各ROW_EVENT都含有m_flags标志位集合。可能的标志如下:

名称 含义
STMT_END_F 1 语句执行结束标志
NO_FOREIGN_KEY_CHECKS_F 2 不进行外键约束检查的标志
RELAXED_UNIQUE_CHECKS_F 4 不进行唯一键约束检查的标志

相关代码如下:

/*

These definitions allow you to combine the flags into an

appropriate flag set using the normal bitwise operators. The

implicit conversion from an enum-constant to an integer is

accepted by the compiler, which is then used to set the real set

of flags.

*/

enum enum_flag

{

/* Last event of a statement */

STMT_END_F = (1U << 0),

/* Value of the OPTION_NO_FOREIGN_KEY_CHECKS flag in thd->options */

NO_FOREIGN_KEY_CHECKS_F = (1U << 1),

/* Value of the OPTION_RELAXED_UNIQUE_CHECKS flag in thd->options */

RELAXED_UNIQUE_CHECKS_F = (1U << 2)

};

typedef uint16 flag_set;

/* Special constants representing sets of flags */

enum

{

RLE_NO_FLAGS = 0U

};

MySQL row方式的复制relay

上次老大问我row方式的binlog复制到备机可不可能记录为statement。根据我对复制的了解和对row方式的理解,我回答的是不可能。因为MySQL的源码中,我记得row方式的处理是table map,row_log_event分开的,然后row_log_event中记录的是行的数据(包括bitmap对应对应的列在该row_log_event有没有记录,整个row_log_event的长度,每个列的长度后面跟上列的具体数据等),这些东西记录下载,MySQL的开发者如果要把它还原成statement方式,并且将相关的auto_increment,var,rand等还原出来难度还是非常大的,并且,row方式实际上已经毁坏了statement的结构(比如:update tbl1 set c1=3 where id=3记录为row的话,正常情况下,row_log_event不会只记录了更新的这一列c1,它会记录id或者其他的列,虽然id或者其他的列值并没有更新。原因可能是为了正确的更新对应的行。),如果想完全还原成和主机上提交的statement一模一样基本是不可能的。另外,我也没有看到MySQL源码中的相关代码有将row转换成statement的相关痕迹,所以判断row方式在备机中log_slave_updates会也被记录为row方式。
但是,口说无凭,实践致胜,我在主备机环境下测试了上面的这个情况。确实如上所述,row方式的binlog,备机利用log_slave_updates记录到本机binlog也是row方式。即使你设置备机的binlog_format为statement。下面是我的测试描述和结果。

1、主机上设置binlog_format为row.
root@localhost : alitest 10:01:09> set binlog_format=row;
Query OK, 0 rows affected (0.00 sec)

root@localhost : alitest 10:01:25> show variables like ‘bin%’;
+——————-+———+
| Variable_name     | Value   |
+——————-+———+
| binlog_cache_size | 2097152 |
| binlog_format     | ROW     |
+——————-+———+
2 rows in set (0.00 sec)

2、备机上设置binlog_format为statement
root@localhost : (none) 10:06:44> set global binlog_format=’statement’;
Query OK, 0 rows affected (0.00 sec)
root@localhost : (none) 10:07:04> set binlog_format=’statement’;
Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:07:11> show variables like ‘bin%’;
+——————-+———–+
| Variable_name     | Value     |
+——————-+———–+
| binlog_cache_size | 2097152   |
| binlog_format     | STATEMENT |
+——————-+———–+
2 rows in set (0.00 sec)

3、主机上创建测试表:
root@localhost : (none) 10:00:02> use alitest;
Database changed

root@localhost : alitest 10:00:50> create table test9 (c1 int unsigned primary key, c2 varchar(24));
Query OK, 0 rows affected (0.20 sec)

4、备机上查看目前的日志位置:
root@localhost : (none) 10:04:49> show master logs;
+——————+———–+
| Log_name         | File_size |
+——————+———–+
| mysql-bin.000001 |       125 |

| mysql-bin.000085 | 362605228 |
+——————+———–+
85 rows in set (0.00 sec)

4、主机上生成测试的row方式日志:
root@localhost : alitest 10:01:27> insert into test9 (c1,c2) values (44, “343434″);
Query OK, 1 row affected (0.00 sec)

root@localhost : alitest 10:02:53> insert into test9 (c1,c2) values (3, “343434″);
Query OK, 1 row affected (0.00 sec)

5、备机查看自己生成binlog:
root@localhost : (none) 10:07:53> show binlog events in ‘mysql-bin.000085′ from 362605228;
+——————+———–+————+———–+————-+——————————–+
| Log_name         | Pos       | Event_type | Server_id | End_log_pos | Info                           |
+——————+———–+————+———–+————-+——————————–+
| mysql-bin.000085 | 362605228 | Query      |         1 |   362605287 | BEGIN                          |
| mysql-bin.000085 | 362605287 | Table_map  |         1 |   362605337 | table_id: 27 (alitest.test9)   |
| mysql-bin.000085 | 362605337 | Write_rows |         1 |   362605378 | table_id: 27 flags: STMT_END_F |
| mysql-bin.000085 | 362605378 | Xid        |         1 |   362605405 | COMMIT /* xid=23537907 */      |
| mysql-bin.000085 | 362605405 | Query      |         1 |   362605464 | BEGIN                          |
| mysql-bin.000085 | 362605464 | Table_map  |         1 |   362605514 | table_id: 27 (alitest.test9)   |
| mysql-bin.000085 | 362605514 | Write_rows |         1 |   362605555 | table_id: 27 flags: STMT_END_F |
| mysql-bin.000085 | 362605555 | Xid        |         1 |   362605582 | COMMIT /* xid=23537917 */      |
+——————+———–+————+———–+————-+——————————–+
8 rows in set (0.00 sec)

由上可以看到,备机虽然设置自己的binlog_format为statement,binlog日志中记录从主机过来的binlog仍然为row。写到这里,我突然想起,binlog_format是否只是对在本机提交的sql才有效,于是我测试了以下两种情况:
1、主机为statement,备机为statement。 结果备机记录为statement.
2、主机为statement,备机为row。结果备机记录为statement.
也就是说,备机记录的从主机复制过来的binlog不随自己的binlog_format方式改变,而是忠实的依照主机记录的方式来记录。
下面是简单的测试结果:
测试1 主机为statement,备机为statement。 结果备机记录为statement.
1、主机上设置binlog_format为row并插入一行
root@localhost : alitest 10:42:41> set binlog_format=statement;
Query OK, 0 rows affected (0.00 sec)

2、备机上设置为statement
root@localhost : (none) 10:06:44> set global binlog_format=’statement’;
Query OK, 0 rows affected (0.00 sec)
root@localhost : (none) 10:07:04> set binlog_format=’statement’;
Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:07:11> show variables like ‘bin%’;
+——————-+———–+
| Variable_name     | Value     |
+——————-+———–+
| binlog_cache_size | 2097152   |
| binlog_format     | STATEMENT |
+——————-+———–+
2 rows in set (0.00 sec)

3、主机上插入一行
root@localhost : alitest 10:42:54> insert into test9 (c1,c2) values (4, “343434″);
Query OK, 1 row affected (0.00 sec)

4、备机上查看日志:
root@localhost : (none) 10:35:48> show binlog events in ‘mysql-bin.000085′ from 362605228;
+——————+———–+————+———–+————-+—————————————————————+
| Log_name         | Pos       | Event_type | Server_id | End_log_pos | Info                                                          |
+——————+———–+————+———–+————-+—————————————————————+
| mysql-bin.000085 | 362605228 | Query      |         1 |   362605287 | BEGIN                                                         |
| mysql-bin.000085 | 362605287 | Table_map  |         1 |   362605337 | table_id: 27 (alitest.test9)                                  |
| mysql-bin.000085 | 362605337 | Write_rows |         1 |   362605378 | table_id: 27 flags: STMT_END_F                                |
| mysql-bin.000085 | 362605378 | Xid        |         1 |   362605405 | COMMIT /* xid=23537907 */                                     |
| mysql-bin.000085 | 362605405 | Query      |         1 |   362605464 | BEGIN                                                         |
| mysql-bin.000085 | 362605464 | Table_map  |         1 |   362605514 | table_id: 27 (alitest.test9)                                  |
| mysql-bin.000085 | 362605514 | Write_rows |         1 |   362605555 | table_id: 27 flags: STMT_END_F                                |
| mysql-bin.000085 | 362605555 | Xid        |         1 |   362605582 | COMMIT /* xid=23537917 */                                     |
| mysql-bin.000085 | 362605582 | Query      |         1 |   362605641 | BEGIN                                                         |
| mysql-bin.000085 | 362605641 | Query      |         1 |   362605753 | use `alitest`; insert into test9 (c1,c2) values (4, “343434″) |
| mysql-bin.000085 | 362605753 | Xid        |         1 |   362605780 | COMMIT /* xid=23537922 */                                     |
+——————+———–+————+———–+————-+—————————————————————+
11 rows in set (0.00 sec)

测试2 主机为statement,备机为row。结果备机记录为statement.
1、主机上设置binlog_format为row并插入一行
root@localhost : alitest 10:42:41> set binlog_format=statement;
Query OK, 0 rows affected (0.00 sec)

2、备机上设置为statement
root@localhost : (none) 10:46:23> set binlog_format=’row’;
Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:46:28> set global binlog_format=’row’;
Query OK, 0 rows affected (0.00 sec)

root@localhost : (none) 10:46:37> show variables like ‘bin%’;
+——————-+———+
| Variable_name     | Value   |
+——————-+———+
| binlog_cache_size | 2097152 |
| binlog_format     | ROW     |
+——————-+———+
2 rows in set (0.00 sec)

3、主机上插入一行
root@localhost : alitest 10:43:02> insert into test9 (c1,c2) values (5, “343434″);
Query OK, 1 row affected (0.00 sec)

4、备机上查看日志:
root@localhost : (none) 10:46:43> show binlog events in ‘mysql-bin.000085′ from 362605228;
+——————+———–+————+———–+————-+—————————————————————+
| Log_name         | Pos       | Event_type | Server_id | End_log_pos | Info                                                          |
+——————+———–+————+———–+————-+—————————————————————+
| mysql-bin.000085 | 362605228 | Query      |         1 |   362605287 | BEGIN                                                         |
| mysql-bin.000085 | 362605287 | Table_map  |         1 |   362605337 | table_id: 27 (alitest.test9)                                  |
| mysql-bin.000085 | 362605337 | Write_rows |         1 |   362605378 | table_id: 27 flags: STMT_END_F                                |
| mysql-bin.000085 | 362605378 | Xid        |         1 |   362605405 | COMMIT /* xid=23537907 */                                     |
| mysql-bin.000085 | 362605405 | Query      |         1 |   362605464 | BEGIN                                                         |
| mysql-bin.000085 | 362605464 | Table_map  |         1 |   362605514 | table_id: 27 (alitest.test9)                                  |
| mysql-bin.000085 | 362605514 | Write_rows |         1 |   362605555 | table_id: 27 flags: STMT_END_F                                |
| mysql-bin.000085 | 362605555 | Xid        |         1 |   362605582 | COMMIT /* xid=23537917 */                                     |
| mysql-bin.000085 | 362605582 | Query      |         1 |   362605641 | BEGIN                                                         |
| mysql-bin.000085 | 362605641 | Query      |         1 |   362605753 | use `alitest`; insert into test9 (c1,c2) values (4, “343434″) |
| mysql-bin.000085 | 362605753 | Xid        |         1 |   362605780 | COMMIT /* xid=23537922 */                                     |
| mysql-bin.000085 | 362605780 | Query      |         1 |   362605839 | BEGIN                                                         |
| mysql-bin.000085 | 362605839 | Query      |         1 |   362605951 | use `alitest`; insert into test9 (c1,c2) values (5, “343434″) |
| mysql-bin.000085 | 362605951 | Xid        |         1 |   362605978 | COMMIT /* xid=23537929 */                                     |
+——————+———–+————+———–+————-+—————————————————————+
14 rows in set (0.00 sec)

附上binlog_format变量的介绍
–binlog-format={ROW|STATEMENT|MIXED}
Version Introduced     5.1.5
Command-Line Format     –binlog-format
Config-File Format     binlog-format
Option Sets Variable     Yes, binlog_format
Variable Name     binlog_format
Variable Scope     Both
Dynamic Variable     Yes
Permitted Values (>= 5.1.5, <= 5.1.7)
Type     enumeration
Default     STATEMENT
Valid Values     ROW, STATEMENT
Permitted Values (>= 5.1.8, <= 5.1.11)
Type     enumeration
Default     STATEMENT
Valid Values     ROW, STATEMENT, MIXED
Permitted Values (>= 5.1.12, <= 5.1.28)
Type     enumeration
Default     MIXED
Valid Values     ROW, STATEMENT, MIXED
Permitted Values (>= 5.1.29)
Type     enumeration
Default     STATEMENT
Valid Values     ROW, STATEMENT, MIXED

Specify whether to use row-based, statement-based, or mixed replication (statement-based was the default prior to MySQL 5.1.12; in 5.1.12, the default was changed to mixed replication; in 5.1.29, the default was changed back to statement-based). See Section 16.1.2, “Replication Formats”. This option was added in MySQL 5.1.5.
Important

Setting the binary logging format without enabling binary logging prevents the MySQL server from starting. This is a known issue in MySQL 5.1 which is fixed in MySQL 5.5. (Bug#42928)

MySQL Cluster.  The default value for this option in all MySQL Cluster NDB 6.1, 6.2, 6.3, and later 6.x releases is MIXED. See Section 17.6.2, “MySQL Cluster Replication: Assumptions and General Requirements”, for more information.

may your success.

Maatkit 简介

Maatkit 简介
1、maatkit工具集简介
maatkit包含多个MySQL的辅助工具,使用它们,可以让你的MySQL服务器使用起来更加方便也更加安全。其实它提供的大部分工具本身就应该是MySQL应该提供的,但是由于各种原因,MySQL更专注于数据库本身的性能和功能的增强,而忽略了这些方面工具的提供,好在percona和其他一些对MySQL本身功能仍然不满足公司组织发起并维护了maatkit项目,他们的贡献和使用进一步促进了maatkit的进步和发展。
maatkit的另外一个好处就是,他是开源的,是一个Free software。你可以自由的改进软件并将自己作出的改进版本向社会发行传播。它遵循GPL协议 。如何获得支持和服务是众所周知的自由软件的问题,但是对maatkit来说,也不是大问题。你可以通过google project获得免费的帮助,如果仍然不够,你甚至可以直接通过percona联系原作者,获得MySQL性能优化的专家顾问们的帮助。
maatkit考虑到自身工具的数量问题,为了减轻它的复杂度,也简化使用者学习的难度,maatkit所有的工具的选项和参数都保持统一的标准(而且大部分参数和MySQL的命令行客户端保持一致),基本上,你会用了一个工具,其他工具的学习和了解也差不多。这样大大减轻了使用者的负担也加速了我们的学习过程。
真是由于maatkit的这么多优势,它目前已经随Debian和CentOS等许多GNU/linux distributions一起发布  。

下面我们先来简单了解一下maatkit包含的工具集为MySQL提供了哪些振奋人心的新特性和新功能。
maatkit工具目前包含26个不同功能的工具。相应的工具名及其功能简介如下:
1.mk-archiver 将MySQL表中的一些行数据库归档到另外一个表或者文件中。Archive rows from a MySQL table into another table or a file.
2.mk-audit 对MySQL配置,架构和操作进行分析,汇总并生成报告。Analyze, summarize and report on MySQL config, schema and operation
3.mk-checksum-filter 过滤mk-table-checksum生成的checksums。Filter checksums from mk-table-checksum.
4.mk-deadlock-logger 抽取和记录MySQL死锁信息。Extract and log MySQL deadlock information.
5.mk-duplicate-key-checker 查找MySQL表中重复的索引和外键索引。Find duplicate indexes and foreign keys on MySQL tables.
6.mk-fifo-split Split files and pipe lines to a fifo without really splitting.暂时没有被包含在fedora 11的源包中。
7.mk-find 类似于GNU的find工具,用于查找MySQL表和执行动作。Find MySQL tables and execute actions, like GNU find.
8.mk-heartbeat 监控MySQL replication延迟时间。Monitor MySQL replication delay.
9.mk-kill kill掉符合某一条件的MySQL queries。Kill MySQL queries that match certain criteria.暂时没有被包含在fedora 11的源包中。
10.mk-loadavg 监控MySQL负载并在负载过大时采取某些动作。Watch MySQL load and take action when it gets too high. 暂时没有被包含在fedora 11的源包中。
11.mk-log-player 分割并重现MySQL的slow logs。Split and play MySQL slow logs. 暂时没有被包含在fedora 11的源包中。
12.mk-parallel-dump 以并行的方式dump MySQL的多个表。Dump sets of MySQL tables in parallel.
13.mk-parallel-restore 以并行的方式将文件Load到MySQL中去。Load files into MySQL in parallel.
14.mk-profile-compact 压缩mk-query-profiler的输出。Compact the output from mk-query-profiler.
15.mk-query-digest 解析日志和相关资料。分析,转换,过滤,复审queries,并生成报告。Parses logs and more. Analyze, transform, filter, review and report on queries.暂时没有被包含在fedora 11的源包中。存在另外的mk-log-parser工具。
16.mk-query-profiler 执行SQL语句并打印统计信息,或者评估其他进程的活动。Execute SQL statements and print statistics, or measure activity caused by other processes.
17.mk-show-grants 规范话和打印MySQL授权信息。这样你可以更有效的复制,比较他们,也可以更好的对他们进行版本控制。Canonicalize and print MySQL grants so you can effectively replicate, compare and version-control them.
18.mk-slave-delay 控制MySQL slave端,使它滞后于其master。Make a MySQL slave server lag behind its master.
19.mk-slave-find 找出并打印MySQL和其slave的层级关系树模型。Find and print replication hierarchy tree of MySQL slaves.
20.mk-slave-move 在MySQL replication层级关系中移动某一个MySQL slave。Move a MySQL slave around in the replication hierarchy.
21.mk-slave-prefetch 将relay log传给MySQL slave以便预热缓存。Pipeline relay logs on a MySQL slave to pre-warm caches.
22.mk-slave-restart 监控MySQL replication并在其出现错误时重启replication。Watch and restart MySQL replication after errors.
23.mk-table-checksum 利用它可以对在线的replication进行一致性检查,或者有效地计算一个或多个服务器上的MySQL表的校验和。Perform an online replication consistency check, or checksum MySQL tables efficiently on one or many servers.
24.mk-table-sync 有效地同步MySQL的表。Synchronize MySQL tables efficiently.
25.mk-upgrade 在两个MySQL server上提交SQL语句,并比较运行结果。Execute SQL statements against two MySQL servers and compare the results. 暂时没有被包含在fedora 11的源包中。
26.mk-visual-explain 将EXPLAIN的输出打印成树状结构。Format EXPLAIN output as a tree.

注意:上面的各项工具及其相应的功能主要翻译自maatkit的官方文档。其中:mk-fifo-split,mk-kill,mk-loadavg,mk-log-player,mk-query-digest,mk-upgrade暂时没有包含在fedora的源中,而fedora的源中包含另外一个官方文档中不存在的工具mk-log-parser工具。

写这篇文档的时候突然看到大头刚有写这个方面的文档,都写了十二份maatkit的工具文档了,我就偷懒了,只列出这12份文档的网络地址如下:
1.Mysql管理必备工具Maatkit详解之一(安装Maatkit)
2.Mysql管理必备工具Maatkit详解之二(mk-archiver)
3.Mysql管理必备工具Maatkit详解之三(mk-audit)
4.Mysql管理必备工具Maatkit详解之四(mk-duplicate-key-checker)
5.Mysql管理必备工具Maatkit详解之五(mk-deadlock-logger)
6.Mysql管理必备工具Maatkit详解之六(mk-show-grants)
7.Mysql管理必备工具Maatkit详解之七(mk-find)
8.Mysql管理必备工具Maatkit详解之八(mk-visual-explain)
9.Mysql管理必备工具Maatkit详解之九(mk-parallel-dump)
10.Mysql管理必备工具Maatkit详解之十(mk-parallel-restore)
11.Mysql管理必备工具Maatkit详解之十一(mk-table-checksum)
12.Mysql管理必备工具Maatkit详解之十二(mk-query-digest)

大头刚还是不错的,再接再厉已经翻译介绍到了15个工具了。后面三个地址先不列出来吧。为他喝彩。

may your sucess.

MySQL的google贡献Google-MySQL-Tools

顺便把以前写的google对MySQL的相关补丁介绍找出来了,部分补丁已经并入MySQL的新版中了。文章贴在这里。权当纪念

娘的,图片不知道怎么发上来,实在有人有兴趣,联系我要word版或者pdf版吧:)
Google-MySQL-Tools  overview
说明

目 录
目 录    2
1    修订记录    3
2    介绍    4
2.1    目的和范围    4
2.2    术语与缩写解释    4
3    详细限制和依赖描述    5
3.1    数据表限制    5
3.2    冲突解决限制    5
3.3    MySQL服务器限制    6
4    MySQL 5.1的新特性    7
4.1    Partitioning分区    7
4.2    Row-based replication    7
4.3    Plugin API.    8
4.4    Event scheduler    8
4.5    Server log tables    8
4.6    Upgrade program    8
4.7    MySQL Cluster replication    8
4.8    MySQL Cluster disk data storage    8
4.9    Improved backups for MySQL Cluster    9
4.10    MySQL Cluster NDB 6.x    9
4.11    Backup of tablespaces    9
4.12    Improvements to INFORMATION_SCHEMA    9
4.13    XML functions with XPath support    9
4.14    Load emulator    9
5    相关文档列表    10
6    附录    11

1修订记录

修订日期    版本号    描述    修订人
2009-02-04    1.0    create    Pickup.Li

2介绍
2.1目的和范围
本文档主要介绍google-mysql-tools工具的相关特性,原理及相关配置注意事项。
google-mysql-tools工具是google公司为了管理,维护和改进MySQL性能的一系列工具,它是一个开源项目,遵守Apache License 2.0协议,编写工具包括python和c++语言。这一系列工具主要包括三个主要的方面:
1)mypgrep.py。python文件,类似于pgrep工具。它是用来查看和管理MySQL连接的。
2)compact_innodb.py。python文件。这个工具通过dumping and reloading所有的table来压缩innodb的数据文件。
3)patches。为MySQL 4.0.26和5.0.37增加一些新特性的一系列patch文件。通过这些文件给MySQL的源代码打上对应的补丁以后,增强了MySQL的功能。目前google-mysql-tools对MySQL5.0.37有两个版本的补丁V1和V2,V2版本是V1版本的加强,并增加了新特性,但目前V2版本仍然是beta版本,没有发布出来。
本文档主要描述的是google-mysql-tools打上V1版本的补丁以后为MySQL增加的新特性。并简单涉及了V2版本的相关内容。

3Google-MySQL-Tools MySQL5 V1 V2已发布特性
3.1Mirrored Binlogs
该patch仅存在于V1版本,在V2版本中它被GlobalTransactionIds patch替换掉了。
3.1.1概述
MySQL本身的replication的效率,稳定性和易用性基本上能够满足一般用户的需求。但是对于那些复杂架构的replication结构有所欠缺。MySQL replication是slave保持着master的binlog名称和偏移量来复制event的。如图1左图所示,relay MySQL从master复制,并作为中继将从master复制的数据记录到本地binlog中(这里其实有一个“翻译”的过程,master的binlog名称和偏移量在relay中都不同了),而slave MySQL则从relay的binlog中读取event。

图1 MySQL replication和Google-MySQL-Tools mirrored binlog比较
Mirrored binlogs利用relay的IO线程来下载和保存一个master的binlog文件,binlog文件名和偏移量和master端的完全一致。
假如,还有另外一个MySQL instance从master复制数据。当master崩溃时,大家想到的第一个办法就是failover到从relay MySQL那里复制数据,因为relay MySQL已经从master中复制了数据,但是relay MySQL的binlog文件名和偏移量和master都不相同,这个新的slave应该从从relay MySQL的那个地方开始复制,这个很难确定而且容易出错。但是relay MySQL如果采用了Mirrored binlog,由于本地保存binlog文件和master端的完全一样,那么从master端failover到slave端时,新的slave可能完全感觉不到切换的过程。

3.1.2原理和实现方法
实际上Mirrored binlogs的原理很简单,它仅仅增加了一个ReplMule的类(sql/repl_mule.h),并修改了MySQL的IO thread的源代码:在IO thread中初始化和实例化该类并在IO thread获得一个event时将它记录到binlog文件中,这个binlog文件名和偏移量保持与master的一样。另外,要注意的是,第一次设置了Mirrored binlog的时候,slave将从master端下载当前的binlog。这个过程可能要花费一定的时间,在下载完成之前,将不会获取下一个master的event。

3.1.3配置
Mirrored binlog有三个配置选项:rpl_mirror_binlog_enabled,rpl_mirror_binlog_no_replicate,sync-mirror-binlog。rpl_mirror_binlog_enabled设置Mirrored binlog功能的有效性。rpl_mirror_binlog_no_replicate启用slave端的Mirrored binlog功能,但是不允许它的slave继续Mirrored binlog 。sync-mirror-binlog类似于sync_binlog,只不过它是针对Mirrored Binlogs的。
在my.cnf中添加了rpl_mirror_binlog_enabled时,需要同时指定MASTER_HOST=”192.168.1.111″, MASTER_USER = ‘repl’, MASTER_PASSWORD = ‘slavepass’等选项以便Mirrored Binlog连接Master获取数据。并且不能指定skip-slave-start,否则Mirrored Binlogs将无效。
如果配置正确,重新启动MySQL后你可以在slave的data目录看到和master的binlog同名的文件,这就是Mirrored Binlogs下载并生成的。另外,在slave端用show master status你看到的将是master的binlog文件名

3.2Semi-synchronous replication

3.2.1概述
MySQL replication本身是异步的,master并不知道slave何时或者是否已经获得了binlog的event。它的效率也是比较高的,slave请求某个binlog文件的某一偏移量开始的replication event,而master在event准备好时将它们发送给slave,这样对master没有很多的性能消耗。
Semi-synchronous replication扩展了replication为半双工的,slave可以配置为async或semi-sync两种不同的方式。如果在master端启用了Semi-synchronous replication,当语句提交返回前都需要阻塞,直到至少收到了一个semi-sync slave的确认或者达到了用户配置的超时时间。如果超过了超时时间,Semi-synchronous replication将不再有效,当slave重新同步成功,Semi-synchronous replication又将重新启用。

3.2.2原理和实现方法
Semi-synchronous replication的主要流程如图2所示,它修改了MySQL replication的协议,以及语句提交的代码。在transanction提交以后将等待slave,这里将有可能超过超时时间;之后,才返回客户端通知操作结果。在等待网络回应的过程中其他的transaction并不会阻赛。在slave连接master并开始请求数据的时候,如果设置了Semi-synchronous replication,那么slave的请求数据的binlog_flag中将设置BINLOG_SEMI_SYNC以注册slave为semi-sync模式。这样的话,master向该slave发送的event将包括一个额外单字节的头,该字节指明slave是否需要向master回应一个消息。
Master端将为所有正在等待回应的transaction建立一个检索树,检索树以(binlog_filename, binlog_pos)为键,方便replication线程找到当前等待的transaction。图3,图4分别为master端transaction commit的流程图和replication thread发送binlog时的流程图。

图2 Semi-synchronous replication示意图

图3 master transaction commit流程图

图4 replication线程发送binlog流程图

3.2.3配置
Semi-synchronous replication有三个配置选项:rpl_semi_sync_enabled,rpl_semi_sync_slave_enabled,rpl_semi_sync_timeout。rpl_semi_sync_enabled用来在master端启用Semi-synchronous replication,而rpl_semi_sync_slave_enabled用于在slave端启用。而commit超时的时间由rpl_semi_sync_timeout设置。另外,用户还可以通过show status查看Semi-synchronous replication的状态变量,例如:semi-sync slave的个数-Rpl_semi_sync_clients,semi-sync是否被启用-Rpl_semi_sync_status等等。
Semi-synchronous replication可以在不重启数据库的情况下停用或者启用。
简单的测试表明,Semi-synchronous replication由于在第一次slave不能跟上的情况下将会自动停用,所以对master的性能影响不大。

3.3SqlChanges
Google-MySQL-Tools对MySQL的SQL分析进行了一定的改变。为MySQL增加了新的tokens,新的函数,新的语句(statements),也为已有的语句增加了新的选项(options)。
3.3.1New tokens
Google-MySQL-Tools为MySQL增加了一些新的token,主要包括:CLIENT_STATISTICS, TABLE_STATISTICS, USER_STATISTICS, INDEX_STATISTICS, IF_IDLE, MAKE, MAPPED, MAX_QUERIES_PER_MINUTE, NEW_PASSWORD, ROLE, SLOW, TCMALLOC, IEEE754_TO_STRING, LAST_VALUE, ORDERED_CHECKSUM, UNORDERED_CHECKSUM。

3.3.2New SQL functions
Google-MySQL-Tools为MySQL增加了一些新的函数。包括:ORDERED_CHECKSUM, UNORDERED_CHECKSUM, LAST_VALUE, HASH, IEEE754_TO_STRING, NEW_PASSWORD。这些函数的具体信息可以通过这里查找到详细信息。参见3.16 NewSqlFunctions

3.3.3New options for existing statements
KILL <id> IF_IDLE可以仅在一个连接空闲的时候切断它。MAX_QUERIES_PER_MINUTE可以用来替代MAX_QUERIES_PER_HOUR,从而对查询的限制从每小时转为每分钟。CREATE MAPPED USER ‘foo’ ROLE ‘bar’和DROP MAPPED USER ‘foo’为Mapped user提供支持。详情请查看MySQLRoles介绍或者这里。相对应的SHOW PROCESSLIST WITH ROLES和SHOW USER_STATISTICS WITH ROLES通过role而不是用户名来再结果中展示进程和用户数据。

3.3.4New statements
Google-MySQL-Tools为MySQL增加了用户表监控函数:SHOW USER_STATISTICS, SHOW TABLE_STATISTICS, SHOW INDEX_STATISTICS, SHOW CLIENT_STATISTICS, FLUSH TABLE_STATISTICS, FLUSH INDEX_STATISTICS, FLUSH CLIENT_STATISTICS。参见3.8节UserTableMonitoring。
Google-MySQL-Tools为MySQL增加了针对帐号和客户端IP速度限制的函数。例如:MAKE USER ‘foo’ DELAYED 1000, MAKE CLIENT ’10.0.0.1′ DELAYED 2000, SHOW DELAYED USER, SHOW DELAYED CLIENT。参见3.11节MysqlRateLimiting。
另外,还包括:SHOW TCMALLOC STATUS用于展示tcmalloc的状态,它只有在在MySQL连接(link) tcmalloc并且编译时指定了-DUSE_TCMALLOC时才有效。实际上它只是展示了MallocExtension::GetStats的输出结果。CAST函数可以转换数据为双精度型。SHOW INNODB LOCKS函数提供了InnoDB锁的占用者holders和等待者waiters的更多详细信息。FLUSH SLOW QUERY LOGS刷新slow query log文件。MAKE MASTER REVOKE SESSION使得除当前会话以外的连接都断开,并且除了有SUPER, REPL_CLIENT或者REPL_SLAVE权限的用户可以连接,其他的用户都不能连接数据库。与之相反的,MAKE MASTER GRANT SESSION取消这种限制。

3.4InnodbSmp
Google-MySQL-Tools修改了MySQL的源代码使得它在SMP服务器上运行更加快速,在8核以上的服务器上更加明显。代码修改包括:对InnoDB的互斥量mutex采用原子内存操作(atomic memory operations)。使用tcmalloc并且禁用了InnoDB的内存堆(memory heap)。对InnoDB的读写互斥量rw-mutex采用原子内存操作(atomic memory operations)。

3.5NewShowStatus
Google-MySQL-Tools为用户监控和查看MySQL的状态提供了更多的信息。Show status中现在宝航了一些show innodb status的信息。对那些频繁查看MySQL状态的工具(overzealous monitoring tools)也进行了速率限制,限制他们不断的进行耗费资源的show操作。更多详细信息见这里。

3.6NewShowInnodbStatus
Google-MySQL-Tools修改了SHOW INNODB STATUS的输出,输出了更多信息。通过输出的重新排序使得事务以列表的形式打印并且可能返回的最大输出大小。更多详细信息见这里。

3.7NewConfiguration
由于Google-MySQL-Tools增加了这么多功能,所以配置文件也有相应的选项增加。更多详细信息见这里。

3.8UserTableMonitoring
Google-MySQL-Tools为每个帐号,数据表,索引记录了数据库动作(database activity) 。同样,为这些数据提供了SQL语句来打印这些信息。并且,Google-MySQL-Tools正在计划把他们整合到information_schema中。
相关的SQL statement有:SHOW USER_STATISTICS, SHOW TABLE_STATISTICS, SHOW INDEX_STATISTICS, SHOW CLIENT_STATISTICS, FLUSH TABLE_STATISTICS, FLUSH INDEX_STATISTICS, FLUSH CLIENT_STATISTICS。更多详细信息见这里。

3.9TransactionalReplication
MySQL本身的replication会记录当前的复制信息到master.info和relay-log.info文件中。SQL thread首先在存储引擎端提交事务,然后更新上述两个文件来指明下一个SQL thread执行的event的偏移量。但是,如果正好在提交事务以后和更新文件之前MySQL被停掉了,那么复制状态就不对了,SQL thread将在MySQL再次启动的时候重新执行上次的最后一个事务。
Google-MySQL-Tools通过在InnoDB的事务日志中保存复制状态来避免上述错误。在MySQL重新启动的时候,保证前面提到的复制信息文件和该状态保持一致。可以通过指定:rpl_transaction_enabled=1来使的该策略生效。一般来说,它应该加到my.cnf的[mysqld]段。更多详细信息见这里。

3.10MysqlRoles
MySQL对成千上万的账号和表的访问控制效果不理想。导致这个问题的原因在于,很多帐户都有一样的权限,而唯一为一个账号限制它的访问权限的办法就是为它在表一级或者列一级分配权限,从而使得mysql.user表存在很多条记录。实际上,权限(privileges)可以和角色(role)对应起来,而一个角色可以和多个账号对应起来。从而,当多个帐号有同样的权限时,可以避免为每一个账号去单独指定一个权限。
Google-MySQL-Tools在MySQL访问控制模式中增加了mapped user来实现这样的功能。一个mapper user提供了权限认证并且对应于访问控制中的一个角色。Mysql.mapped_user表就是为了定义mapper user的表。Mysql.user表则被当作角色的表。也就是说:mapped user对应于mysql.mapped_user表中的一行记录,role对应mysql.user的一行记录,而同时又对应着多个mysql.mapped_user中的记录(对应列为:mysql.mapped_user.Role)。
通过对访问控制模式的修改,Google-MySQL-Tools有如下特性:一个帐户可以有多个密码;手动的密码过期机制;角色的概念;这个修改对客户端透明,客户端连接完全不需要修改。更多详细信息见这里。

3.11MysqlRateLimiting
Google-MySQL-Tools为MySQL的每个用户或者客户端IP增加了速度控制。MAKE USER ‘foo’ DELAYED 1000使得foo用户提交的SQL语句在执行前先sleep 1000毫秒。当延迟设置为0时表示没有延迟。对应的用户延时值可以通过SHOW DELAYED USER打印出来。这些延时值是易失的,在MySQL重启之后都被设置为0。对于已有的连接,延时设置是无效的,只有该用户在重新连接的时候才会获得新的延时值。另外要注意的是,MySQL本身通过mysql.user. max_questions列为一个账号指定了每个小时可以执行的语句个数,Google-MySQL-Tools把它改为每分钟可以执行的语句个数。
MAKE CLIENT ’10.0.0.1′ DELAYED 2000对IP地址为10.0.0.1提交的语句sleep 2000毫秒,这些延时值通过SHOW DELAYED CLIENT获得。更多详细信息见这里。

3.12MoreLogging
Google-MySQL-Tools为MySQL增加了更多的日志记录选项:audit_log记录了用户登录信息,对指定表的查询语句(用log_tables选项来指定,各表名用分号分隔)和启动的相关信息。Log-update记录有SUPER权限的用户提交的DDL和DML语句。更多详细信息见这里。

3.13InnodbAsyncIo
InnoDB在Windows环境下支持异步IO,在Linux环境下将启动4个线程来完成后台的IO任务,这些线程都采用同步IO。
在不使用直接IO(direct IO)而采用缓存IO(buffered IO)或者远程IO(remote disk)的架构里,写线程(write threads)个数并不需要太多,因为写到操作系统的缓存中非常快速。但是如果内存非常大,使用直接IO的效率会更高。
Google-MySQL-Tools修改了InnoDB相关代码,使得用户可以配置读写请求的后台IO线程个数。配置参数如下:innodb_max_merged_io:后台IO线程合并提交的IO请求的最大个数。innodb_read_io_threads:读取预取请求(prefetch requests)的后台IO线程的个数。innodb_write_io_threads:从缓存中向脏页(dirty pages)写的后台IO线程个数。更多详细信息见这里。

3.14FastMasterPromotion
Google-MySQL-Tools提供了一些命令来允许快速的将slave升级为master,它是在slave不重新启动的情况下升级的,有脏页(dirty pages)的存储引擎,例如InnoDB需要耗费很长时间(大于一分钟)来停止。以下命令在V1和V2补丁中都存在:MAKE MASTER REVOKE SESSION, MAKE MASTER REVOKE SESSION WITH KILL, MAKE MASTER GRANT SESSION。而以下命令仅存在于V1补丁:MAKE MASTER MASTER_LOG_FILE=<log_file>, MASTER_SERVER_ID=<id> BINLOG, MAKE MASTER MASTER_LOG_FILE=<log_file>, MASTER_SERVER_ID=<id>, INDEX=<log_file.index> BINLOG。
MAKE MASTER MASTER_LOG_FILE使得slave在不重启mysqld的情况下使用对应的binlog文件。MAKE MASTER REVOKE SESSION阻止那些没有SUPER权限的用户连接,当指定了WITH KILL选项时当前的没有SUPER权限的连接将被断开。MAKE MASTER GRANT SESSION则取消上述限制,允许那些没有SUPER权限的用户连接。更多详细信息见这里。

3.15InnodbSampling
InnoDB使用取样(sampling)来确定进一步的优化统计(optimizer statistics)。它采用8个叶子节点(leaf node)的索引键。而Google-MySQL-Tools增加了一个会话参数(session parameter)来设置取样的叶子快(leaf block)个数,缺省为8。对应的参数为:innodb_btr_estimate_n_pages。更多详细信息见这里。

3.16NewSqlFunctions
Google-MySQL-Tools为MySQL添加了一些新的函数。
IEEE754_TO_STRING:转换浮点型(float)或者双精度型(double)数据为字符串型(string)。转换成的字符串为17位的数字精度(17 digits of precision),这样从字符串转换回双精度型就不会损失精度。这样,除非在特殊情况下,转换回的双精度型数据和原来的相等。
UNORDERED_CHECKSUM:这是一个SQL聚合函数(SQL aggregate function),接受一个或者多个参数,返回每个group的输入参数的hash值(returns the hash of its input arguments per group)。该函数与order独立。Group中的每一个row的结果是用异或(XOR)连接起来的。用户可以像这样调用该函数:select unordered_checksum(c1, c2) from foo group by c3;
ORDERED_CHECKSUM:和上面的函数一样,不过它跟order有关,group中的第一个row将作为下一个row的hash的种子。
HASH:这是一个SQL函数(SQL function),返回输入参数的hash值,它不是一个聚合函数,对每个row将返回一个值。
LAST_VALUE:这是一个SQL聚合函数(SQL aggregate function),它返回每个group读取的最后一个值。也就是说它依赖于聚合的输入顺序(input order to aggregation)。可以参考OnlineDataDrift。
NEW_PASSWORD:计算新密码(new-style password)的hash值,而不管my.cnf中的old_passwords参数值。
更多详细信息见这里。

3.17InnoDBStatus
Google-MySQL-Tools为MySQL的show innodb status增加额外的输出信息。包括:当前的事务信息段(current transaction section)太长了而被截断的时候而打印完整的信息(print last);返回的数据量从64kb增加到了128kb;在show innodb status中提供了更加丰富的信息。额外的统计信息包括:每次读或者写请求的平均时间;调用fsync的源;调用同步函数同步InnoDB事务日志的源;后台IO线程完成的工作量等。更多详细信息见这里。

3.18LosslessFloatDump
Google-MySQL-Tools使得MySQL备份和恢复(dump and restore)列数据为浮点型(float)或者双精度型(double)时没有精度上的损失。用户可以在mysqldump命令里加上选项—lossless-fp来启用这个特性。这样,备份时,浮点和双精度型的数据将被转换为17位精度的数字字符串(decimal string with 17 digits of precision)。更多详细信息见这里。

3.19MysqlHttp
Google-MySQL-Tools为MySQL增加了HTTP的内容。更多详细信息见这里。

3.20InnodbIoTuning
Google-MySQL-Tools修改了InnoDB相关代码来调整输入输出。InnoDB利用后台线程来进行IO操作,包括:purge thread物理删除那些被逻辑删除的行;insert buffer thread更新二级索引(secondary indexes);log thread进行事务日至IO的操作;write thread将缓存脏页(dirty buffer cache pages)写入磁盘;read thread预取磁盘块(prefetches blocks)。一般来说,一个线程无法完成读和写的任务,所以Google-MySQL-Tools增加了一些配置参数来设置线程个数:innodb_read_io_threads,innodb_write_io_threads。参见3.13 InnodbAsyncIO。
Google-MySQL-Tools为了避免后台IO线程耗尽服务器资源增加了速度限制的功能。这里假设服务器最大可以做100个IOPs,用户可以设置innodb_io_capacity来指定最大磁盘IOPs数。另外innodb_extra_dirty_writes可以在dirty pct比最大值小的时候刷新缓存脏页(dirty buffer page)。

3.21MutexContentionStats
这个补丁是在x86-64的linux 2.6上用gcc构建的,不知道在其他平台下好不好用。
该补丁为MySQL提供了互斥量争用统计信息(mutex contention statistic),它和show mutex status提供的InnoDB的互斥量输出信息类似。
它同样在请求锁的阻塞过程前(before blocking on lock request)使用忙等循环(busy-wait loops)。这么做的主要原因是为了确定是否有互斥量争用的情况(mutex contention)。如果在忙等循环中不能获得锁,代码就会假设调用者(caller)在请求锁的时候应该要阻塞。忙等循环的持续时间由my.cnf中的参数mysql_spin_wait_loops确定,默认值为500-在X86-64的CPU上对应为4毫秒的延迟。在mysqld启动时将获得该延迟并且输出在数据库的error log日志文件中。同样在show status的变量Mysql_spin_wait_microseconds中也可以查看。
对于指定的锁请求来说,忙等时间是从1到Max(Max为变量mysql_spin_wait_loops的值)中的一个随机数。Google-MySQL-Tools修改了这个计算随机值的函数,使用从InnoDB中的代码,而不是用libc中的random()函数。因为random()函数本身就有互斥量争用的问题。
忙等循环(busy-wait loop)将循环四次(four rounds)。在每次循环中,它都会尝试提交一个非阻塞的锁请求(makes a non-blocking lock attempt)。如果没有得到锁,那么spin 1毫秒(spins for 1 microsecond)。如果四次都失败了,它将提交一个阻塞型的锁请求,并且增加sleeps counter。Spin的主要目的是为了检查互斥量争用(mutex contention),它并不是用来提升性能的。它应该用在性能测试环境(performance debugging builds)而不是生产环境(production builds)。在构建时可以通过指定标志:–with-fast-mutexes来启用它。
SHOW GLOBAL MUTEX STATUS命令可以显示大部分信息。更多详细信息见这里。

3.22FastMutexes
Google-MySQL-Tools将MySQL5.1中的快速互斥量(fast mutexes)backport到以前的版本,并实现了MutexContentionStat。编译时指定—with-fast-mutexes就可以启用它。更多详细信息见这里。

3.23InnodbFreeze
Google-MySQL-Tools可以冻结InnoDB的文件系统的活动。通过set global innodb_disallow_writes=ON,set global innodb_disallow_writes=OFF可以实现这样的目的。它们允许或者阻止用户进行除读取以外的InnoDB文件系统的操作。如果你想在不停止MySQL的情况下备份数据库,并且你也没有使用LVM,ZFS或者其他可以提供快照(snapshots)的存储软件(storage software),那么你可以使用上述命令来阻止InnoDB对文件的修改操作(halt all destructive file system activity from InnoDB),然后就可以备份InnoDB的数据文件。注意:FLUSH TABLES WITH READ LOCK并不能达到这个目的,因为InnoDB的后台IO线程仍然可能做一些IO操作。更多详细信息见这里。

4Features in the V2 beta patch
Google-MySQL-Tools的V2 patch目前并没有发布出来。我们这里简要介绍一下它的几个简单特性。
4.1GlobalTransactionIds
Google-MySQL-Tools让slave能够直接从一个master切换到另外一个master。它提出了一个Global Group ID的概念。每个MySQL instance都有一个统一的Global Group ID,并且在复制的过程中Global Group ID不会变化。这个ID将加到binlog 的事件头中,一个group中的所有事件event的ID都相同,并且group ID都是唯一并且比之前的group ID要大。这个group ID同样也要写到slave的relay log中。这样,slave的SQL thread分析执行event的时候也要解析group ID并且把它保持不变的写到自己的binlog中。这样,当fail over到新的master的时候,它就可以从以前的master的group ID号继续。更多详细信息见这里。

4.2OnlineDataDrift
这个工具主要做以下事情:1、评估数据库里面的row的数目来决定它运行的速度。2、对每一个表执行一系列语句来计算一些行(a chunk of rows)的校验和(checksum)并把结果记录到一个表中。当它运行完时,就可以比较master和slave的存放结果的表。如果两者有不同,那就说明有数据偏差(data drift)。更多详细信息见这里。

4.3MysqlThreadPool
Google-MySQL-Tools为MySQL增加了线程池的代码。

5相关文档列表
Google-MySQL-Tools官方网站

6附录

may your sucess.