Archive for 六月 2010

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.

squid反向代理:消息的可缓存性

最近,我们团队计划将memcached运维起来,突然想起之前自己做过相关squid反向代理的文章,有点时间了,翻出来都有点发霉了,呵呵,先保存在这里,有空回忆一下,哈哈。

Hosting Imporvement
消息的可缓存性

目 录
目 录 2
1 修订记录 3
2 介绍 4
2.1 目的和范围 4
2.2 术语与缩写解释 4
3 可缓存实体 5
3.1 按状态码分类的response可缓存性 5
3.2 cache-control response可缓存性 5
3.3 Response响应的过期与保鲜 8
3.4 Validation验证 10
4 其他建议 11
4.1 进一步工作 11
5 相关文档列表 12

1修订记录

修订日期 版本号 描述 修订人
2008/06/27 0.1 消息的可缓存性v0.1 Pickup.Lichun

2介绍
2.1目的和范围
本文档的目的介绍HTTP消息的缓存以及相关信息。
cache服务器作为反向代理可以减少后端web服务器的访问压力并提高用户的访问速度,但是cache服务器到底可以缓存那些东西,可以缓存多久,怎么控制某个消息过期以后的动作等等。这些都是本文档关注的内容。
本文介绍的内容主要来源是HTTP 1.1,某些缓存服务器通过一定的配置(比如:squid配置中refresh_pattern的ignore-private等)能够显式的违反某些规则,所以具体的某个消息的缓存还依赖于缓存服务器的配置及相关因素,需要具体问题具体分析。
另外,本文关注的主要是做反向代理的缓存服务器。

2.2术语与缩写解释
编号 术语 解释
1. message 消息,HTTP通讯的基本单元,通常指request或者response消息
2. request 请求,客户端向服务器发送的请求
3. response 响应,服务器对客户端请求的响应
4. cache 缓存,存储在本机的response信息,以及对这些信息的存储,获取,删除等。这些信息存储以便在相同的request请求时能够及时返回,从而减少响应时间和网络流量的消耗。
5. cacheable 可缓存,某个response信息如果可以存储在下来以供接下来同样的request请求使用,那么这个信息可以被认为作可缓存的。即使某个response是可以缓存的,当一个特定的request请求到达时,是否返回该response还要受到一些附加的条件限制
6. explicit expiration time 显式的过期时间,源服务器指定的过期时间,当过了过期时间以后cache服务器返回相应response之前必须经过验证。
7. heuristic expiration time 启发式的过期时间,当没有显式的过期时间时由cache服务器生成的过期时间
8. age 年龄,一个response的年龄值就是它从被源服务器发送或者验证之后经过的时间值
9. freshness lifetime 保鲜时间,它表示从response生成到过期之间经过的时间值
10. fresh 保鲜的,如果一个response的age没有超过freshness lifetime则被认为是保鲜的
11. stale 过期的,如果一个response的age超过了freshness lifetime则被认为是过期的
12. validator 验证子,用于确认cache保存的实体是否和源服务器一致的协议元素(比如:Last Modified time或者一个entity tag)
13. Shared and Non-Shared Caches
共享和不可共享cache,不可共享cache是指那些只能被单个用户访问的cache,访问它必须经过相关的权限认证,其他的cache都是共享cache。

3可缓存实体
在Squid Cache服务器安装配置与测试数据中,经过测试和分析,发现cache服务器不仅可以为web服务器分担压力,并且能够提高用户访问时的速度。以squid为例,网站用 squid 加速,目的有二
1: squid 本身具有缓存功能,可以将webserver输出的内容缓存起来,在缓存没有过期之前来的访问,都直接用缓存里面的内容,这样可以有效减少 webserver 机器上面的请求数量。这是 squid 的主要功用。
2: 网络慢的用户会长时间占用 webserver 的 TCP 连接,webserver 对每个连接占用的资源比较大,如果长时间不能释放出来服务其他请求,性能会有比较大的影响。前面放一个 squid,webserver 就可以迅速处理完逻辑以后,把数据快速发送给 squid, 然后去处理别的逻辑,而 squid 每个 TCP 连接占用的资源很少,不用担心占用太多资源。这个用途也叫做连接管理,有一些网络设备也可以做这个事情,价格都很贵。
所以我们有必要搞清楚哪些东西可以缓存,哪些不能被缓存,怎么控制,为网页制作人员和web service维护配置人员提供指导。

3.1按状态码分类的response可缓存性
我们知道,response按照状态码可以分成5类,分别是1xx信息类,2xx成功类,3xx重定向类,4xx客户端错误类和5xx服务器错误类。这里可以被缓存的response只有状态码为:200, 203, 206, 300, 301 或者 410的response。其他的response除非由cache-control或者其他的header显式指定(比如:cache-control中的 “max-age”, “s-maxage”, “mustrevalidate”,“proxy-revalidate”, “public” 或 “private”),否则都不能被缓存。

3.2cache-control response可缓存性
cache-control定义了一系列request/response链上的cache服务器必须要遵守的规则。这些规则大部分会改变缺省的cache算法。cache-control的基本语法格式为:
Cache-Control = “Cache-Control” “:” 1#cache-directive
1#cache-directive表示一个或者多个cache-directive,cache-directive可能是cache-request-directive也有可能是cache-response-directive。
cache-request-directive包括:”no-cache”, “no-store”, “max-age=’delta-seconds'”, “max-stale[=’delta-seconds’]”, “min-fresh=’delta-seconds'”, “no-transform”, “only-if-cached”和cache-extension。其中,”max-stale”可以指定以秒为单位的时间值,”max-age”和”min-fresh”则必须指定以秒为单位的时间值。
cache-response-directive包括:”public”, “private[=’1#field-name’]”, “no-cache[=’1#field-name’]”, “no-store”, “no-transform”, “must-revalidate”, “proxy-revalidate”, “max-age=’delta-seconds'”, “s-maxage=’delta-seconds'”, cache-extension。其中,”max-age”和” s-maxage”必须指定以秒为单位的时间值,” private “和” no-cache “可以带一个或者多个HTTP header域从而指定这些header不能缓存。cache-extension是类似community=”UCI”的cache-control扩展标签,它可以用于自定义的cache缓存协议元素,比如community=”UCI”作为Private的附带元素可以表示该条response信息只能被私有的标识为“UCI”的cache缓存,其他的cache都不能缓存它。
首先,我们介绍一些影响response可缓存性的cache-control规则:”public”, “private[=’1#field-name’]”, “no-cache[=’1#field-name’]”。
”public”表示该response可以被缓存,就算平常被认为不能缓存或者只能被non-shared cache缓存的那些response也可以。
”private”表示该response的全部或者某一部分是为单个用户服务的,不可以被shared cache缓存,源服务器可以用它来标示该response隶属于某个用户而不能为其他用户服务。私有(non-shared)cache可以保存这些消息,但是”private”并不能保证消息内容的私密安全性。
”no-cache”,如果no-cache没有特别指定某些field-name,那么它表示cache如果需要返回该response给接下来的request之前必须先向源服务器验证,否则不能发送。这样,源服务器就可以用它来强制cache验证该response,即使cache被设置为可以返回过期的response给用户。如果no-cache附带了一些field-names,cache可以受限的返回response,也就是说返回的response中对应的这些特定的field-names在返回之前必须经过验证,这样源服务器可以限制某些header不会被重用,从而使得这些header都是最新的。
接下来我们来看看影响cache存储response的cache-control规则:”no-store”。
cache可存储reponse和response的可缓存性是不同的。为了避免某些敏感数据不被cache存储下来,可以指定”no-store”,它作用于整个request或者response消息。指定了”no-store”的request表示该request以及由它引起的response都不能被存储,指定了”no-store”的response表示该response以及引起它的request都不能被存储。它对non-shared cache和shared-cache都有效。也就是说cache不能故意的把它存储在永久存储设备上,并且在转发了它以后应该及时清除易失存储设备上的信息。”no-store”提供了一定的私密安全保证,但是某些cache也许不会遵守该约定,也有可能被浏览器本身缓存下来了(当用户点击后退按钮可以访问),并且也可以通过网络监听获得对应的数据。
接着介绍一下影响过期机制的一些cache-control规则:”max-age”, “s-maxage”,”max-stale”和”min-fresh”。其中”max-age”可以应用于request或者response。
作用于response的影响过期机制的一些cache-control规则
“max-age”:过期时间有源服务器的”Expires”头指定,也可以由”max-age”规则指定。”max-age”优先于”Expires”头(即使”Expires”头限制的时间值更加严格),当一个response年龄值大于过期时间时,它被认为是过期的,返回过期的数据需要经过验证或者后面提到的cache-control规则的作用。事实上,”max-age”暗示了该repsonse可以被缓存,除非指定了一些别的更加严格的cache规则。HTTP 1.0没有制定”max-age”规则。
“s-maxage”:”s-maxage”总是被private cache忽略。对于shared cache,它指定了response的最高生命期。它的优先级比”max-age”和”Expires”都要高。”s-maxage”在语义上包含了”proxy-revalidate”,也就是说,cache服务器在返回一个过期的response之前必须要首先提交源服务器进行验证。
作用于request的影响过期机制的一些cache-control规则
“max-age”:在request中包含”max-age”表示客户端同意接受一个age小于指定秒数的response。如果没有指定”max-stale”那么客户端并不同意接受一个过期的response。
“min-fresh”:表示客户端同意接受一个freshness_lifetime不小于当前age值+指定秒数的response。也就是说客户端希望收到的response的新鲜度至少在指定秒数以内。
“max-stale”:表示客户端同意接受一个过期的response消息。如果指定了时间值,表示客户端同意接受一个超过过期时间不大于指定秒数的response消息。如果没有指定具体的数值,表示客户端同意接受一个超过过期时间任意值的response消息。但是,不管是由于用户指定了”max-stale”还是cache服务器被配置为返回过期的数据,cache服务器必须给这个过期的数据加上Warning 110(Response is stale)头。
我们发现,”max-age”即可以应用于request也可以应用于response,如果request和response都包含了”max-age”,那么应该以它们两个中的值更小的那一个为准。
上面,我们提到了cache数据过期后需要提交源服务器进行验证,cache-control同样存在影响验证的规则:”max-age”,”only-if-cached”,”must-revalidate”和”proxy-revalidate”。
“max-age”:客户端提交request的时候可以加上不带field-names的”no-cache”(如果是HTTP 1.0客户端可以用”Pragma: no-cache”)来获得源服务器上的response版本从而使cache也装入这个版本。如果使用”max-age=0″附带于request上,可以使得从客户端到源服务器链路上的所有cache已有的对应response都提交源服务器进行验证,并且把该response装入那些没有存储该response的cache服务器中。当链路中的某台cache服务器收到”max-age=0″的request请求,并且该请求中还附带了validator,而该validator于cache验证的validator不同时,cache服务器可以选择自己的或者附带的validator(比较好的方法是使用cache服务器自己的validator),这样在服务器收到200或者304响应的时候可以根据具体的情况更新缓存,并给客户端发送正确的响应。另外,如果request中包含了”no-cache”就不应该包含”max-age”,”max-stale”和”min-fresh”。
“only-if-cached”:在某些情况下(比如网络情况非常差),客户端可能希望获得cache服务器中存储的response,而要求验证或者从源服务器重新获得对应response消息,用户就可以在request中包含”only-if-cached”。收到这样的请求的cache服务器可以返回本地存储的相应response,或者504(Gateway Timeout)错误信息。如果它属于构建的cache服务器集群,那么它也可以将这个请求转发给集群的其他cache服务器。
“must-revalidate”:上面提到,用户指定”max-stale”或者cache服务器被配置为返回过期的数据都可以导致过期消息的返回。而”must-revalidate”是源服务器用于指定response在过期后必须到源服务器进行验证的规则,不管用户是否指定了”max-stale”或者cache服务器是否被配置为返回过期的数据。它为某些协议特性提供了可靠性操作。如果cache验证时不能连上源服务器,它必须返回504(Gateway Timeout)错误信息。
“proxy-revalidate”:除了不能够应用于non-shared cache以外,”proxy-revalidate”和”must-revalidate”的意思是一样的。它允许在用户cache中保存那些经过权限认证的response(它们 应该包含有”public”来保证它们可以被缓存)并且在认证过同样的request到达时返回缓存数据而不经过源服务器验证过程;其他的则每次都必须经过验证。
最后我们介绍一下”no-transform”规则。为了某些原因,一些cache服务器在保存和传输消息的时候可能会转变实体主体的媒体类型。比如为了降低缓存空间或者在较慢的链接中减少传输流量,cache服务器可能会把一个图片的格式转换一下。但是某些应用场景下,数据的传输一个bit也不能转换,这里”no-transform”就非常必须了。如果一个消息中包含了”no-transform”,那么非修改头部及其指定的消息主体部分都不能转换或者改变,这些非修改头部包括”Content-Location”, “Content-MD5”, “ETag”, “Last-Modified”, “Expires”, “Content-Encoding”, “Content-Range”, “Content-Type”(参见HTTP 1.1 section 13.5.2)
一般说来:遵循以下基本的规则
1.如果响应头信息:告诉缓存器不要保留缓存,缓存器就不会缓存相应内容;
2.如果请求信息是需要认证或者安全加密的,相应内容也不会被缓存;
3.如果在回应中不存在校验器(ETag或者Last-Modified头信息),缓存服务器会认为缺乏直接的更新度信息,内容将会被认为不可缓存。
4.一个缓存的副本如果含有以下信息:内容将会被认为是足够新的
o含有完整的过期时间和寿命控制头信息,并且内容仍在保鲜期内;
o浏览器已经使用过缓存副本,并且在一个会话中已经检查过内容的新鲜度;
o缓存代理服务器近期内已经使用过缓存副本,并且内容的最后更新时间在上次使用期之前;
o够新的副本将直接从缓存中送出,而不会向源服务器发送请求;
5.如果缓存的副本已经太旧了,缓存服务器将向源服务器发出请求校验请求,用于确定是否可以继续使用当前拷贝继续服务;

3.3Response响应的过期与保鲜

3.3.1age的计算方法
先介绍一下一个响应的age的计算方法。
/*
* age_value: is the value of Age: header received by the cache with this response.
* date_value: is the value of the origin server’s Date: header
* request_time: is the (local) time when the cache made the request that resulted in this cached response
* response_time: is the (local) time when the cache received the response
* now: is the current (local) time
*/
apparent_age = max(0, response_time – date_value);
corrected_received_age = max(apparent_age, age_value);
response_delay = response_time – request_time;
corrected_initial_age = corrected_received_age + response_delay;
resident_time = now – response_time;
current_age = corrected_initial_age + resident_time;

3.3.2过期时间的计算方法
过期时间主要分为explicit expiration time和heuristic expiration time,强烈建议用户指定explicit expiration time,因为只有网页制作者更加清楚这个网页的过期时间。
先介绍一下explicit expiration time的计算方法。
客户可以通过设置Expires或者Cache-control的max-age(max-age优先级大于Expires)来指定explicit expiration time。我们测试了html和php语言设置Expires的方法,并且使用ethereal观察了传输的HTTP信息,在我们的环境下,发现使用html设置header meta不能反映到HTTP header中去,而php的header()函数则能够成功。
php设置HTTP头的方法很简单,只要调用函数header()就可以了,假如我们新建一个名为testHeader.php的文件并将其内容修改为:

当用户访问该网页时我们可以传输的信息找到这两个(Expires和Content-Language)HTTP头。
meta是用来在HTML文档中模拟HTTP协议的响应头报文。meta 标签用于网页的与中,meta 的属性有两种:name和HTTP-equiv。name属性主要用于描述网页,以便于搜索引擎机器人查找、分类。HTTP-equiv顾名思义,相当于HTTP的文件头作用,它可以向浏览器传回一些有用的信息,以帮助正确和精确地显示网页内容,与之对应的属性值为content,content中的内容其实就是各个参数的变量值。
meat标签的HTTP-equiv属性语法格式是:<meta HTTP-equiv=”参数” content=”参数变量值”> ;其中HTTP-equiv属性主要有以下几种参数:
A、Expires(期限)
说明:可以用于设定网页的到期时间。一旦网页过期,必须到服务器上重新传输。
用法:<meta HTTP-equiv=”expires” content=”Fri, 12 Jan 2001 18:18:18 GMT”>
注意:必须使用GMT的时间格式。
B、Pragma(cache模式)
说明:禁止浏览器从本地计算机的缓存中访问页面内容。
用法:<meta HTTP-equiv=”Pragma” content=”no-cache”>
注意:这样设定,访问者将无法脱机浏览。
C、Refresh(刷新)
说明:自动刷新并指向新页面。
用法:<meta HTTP-equiv=”Refresh” content=”2;URL=HTTP://www.webjx.com”>
注意:其中的2是指停留2秒钟后自动刷新到URL网址。
D、Set-Cookie(cookie设定)
说明:如果网页过期,那么存盘的cookie将被删除。
用法:<meta HTTP-equiv=”Set-Cookie” content=”cookievalue=xxx; expires=Friday, 12-Jan-2001 18:18:18 GMT; path=/”>
注意:必须使用GMT的时间格式。
E、Window-target(显示窗口的设定)
说明:强制页面在当前窗口以独立页面显示。
用法:<meta HTTP-equiv=”Window-target” content=”_top”>
注意:用来防止别人在框架里调用自己的页面。
F、content-Type(显示字符集的设定)
说明:设定页面使用的字符集。
用法:<meta HTTP-equiv=”content-Type” content=”text/html; charset=gb2312″>
我们在index.htm中的

中加入了:

但是用户访问网页时返回的response的HTTP header中找不到Expires。

接下来,介绍一下heuristic expiration time的计算方法。
而freshness_lifetime计算方法如下:
如果存在cache-control的max-age(或者s-maxage)值,则:freshness_lifetime = max_age_value
否则如果存在Expires值,则:freshness_lifetime = expires_value – date_value
如果上述两个值都不存在,而且响应中也没有包含其他的限制条件时,cache服务器可以使用一种启发式规则来计算获得freshness_lifetime(如果计算得到的freshness_lifetime大于24小时并且年龄值也超过24小时则必须在response响应中增加warning 113),但响应中包含有Last-Modified值时,原则上freshness_lifetime不应该超过从Last-Modified到现在经过的时间值的一定比例(比如:10%)。这个计算freshness_lifetime启发式规则是由cache服务器自己制定的。比如,squid的启发式规则主要是由用户制定的refresh_pattern规则指定。

3.3.3过期或者保鲜
当age值和freshness_lifetime都得到了以后,计算一个响应是否过期就简单了:
response_is_fresh = (freshness_lifetime > current_age)

3.4Validation验证
在HTTP1.1中对缓存进一步提出了验证的概念。验证的目的就是检验缓存项目是否在有效期内。当cache服务器存在一个过期的消息,并且对应的request请求到达时,它应该首先向服务器或者链路上的其他保存有未过期的cache服务器请求验证来确定本地的response是否可用。这个过程就是一个cache消息的验证过程。HTTP 1.1提供了有条件的请求返回方法(conditional methods),这样当本地response是可用时就可以减少网络流量不用传输整个response的信息,而当本地response不可用时也可以减少链路上请求多一个来回的消耗。当源服务器生成了一个完整的response时,它会附带一个验证子(validator);cache服务器可以保存它,在response过期以后,可以利用它生成一个有条件的request以向源服务器请求验证。而服务器(源服务器或者链路上保存有未过期的cache服务器)收到这样的请求以后就可以用它与自己的validator比较,如果相等,那么只返回一个带有特定状态码(通常是:304 Not Modified),并且消息主体为空的response,在这种情况下就减少了网络流量;如果两个validator不相等就传输一个完整的response,在这种情况下避免了客户端再次提交request,从而减少了一次通讯往返。事实上,所谓的有条件的request和普通的request是一样的,只是它包含了一个特殊的Header和validator。并且有条件的验证包括正验证和负验证,如果request中要求服务器与附带的validator必须相等的是正验证,如果要求两者不相等的是指负验证。
validator主要包括Last-Modified Dates和Entity Tag Cache Validators两种,分别对应了”Last-Modified”和”ETag”两个Header。当一个response在”Last-Modified”之后没有被修改过可以认为它是有效的。它是被经常用来验证一个response是否有效的validator。它对应的对应的request请求头包括:”If-Modified-Since”,”If-Unmodified-Since”。但是,”Last-Modified”是有缺陷的,因为HTTP协议的时间值是以秒为单位的,如果一个response在一秒内被修改了两次,那么验证某个response是否一致就没有办法了。所以在这种情况下或者源服务器希望避免使用时间的修改来匹配两个数据时,就可以用”ETag”。”ETag”可以由源服务器指定计算方法,根据response生成,它是一个不透明的validator。对应的request请求头包括:”If-Match”,”If-None-Match”,”If-Range”。
两个validator之间的比较可以分为强验证子比较或者弱验证子比较。强验证子比较表示每一个字节都要相等,而弱验证子比较主要是指实体相等,可以在不明显改变语义的基础上相互替换。弱验证子比较仅仅用于在弱验证子上的比较,比如:在Entity Tag前面有”W/”则表示这是一个弱validator。一个实体的修改时间可以被认为是弱验证子。弱验证子可以应用于不要求精确一致的情况下,强验证子比较应用比较广泛,几乎所有的场合都可以用。如果用户提交的request只需要response的一部分(sub-range)那么必须要保证使用强验证以获得正确的数据。

4其他建议

4.1进一步工作
了解apache和squid的cache实现以及相关配置,并结合本公司网站以及相关数据部署和测试。加快用户访问速度,提交同时可访问的用户数,加强它们的鲁棒形以及服务可用性。为以后公司的产品和项目提供支持。
另外,研究网页制作者增加缓存头部域需要遵守的规范,为网页制作者有意识的增加缓存,控制缓存时间等提供参考。

5相关文档列表
HTTP 1.1
Squid Cache服务器安装配置与测试数据
squid服务器用户手册 v0.2

zfs snapshot磁盘开销

我们的部分MySQL是安装在solaris的zfs文件系统上的,zfs默认的recordsize为128k,但是实际上MySQL的页为16k,为了修改MySQL数据文件的的recordsize参数,我们决定把已有的文件mv出去,修改对应文件系统recordsize为16k以后,然后mv进来。
由于磁盘空间比较少,所以我特地每个机器上都看了一下。确认了磁盘剩余空间比MySQL需要移动的数据多了两倍左右,才开始做,
结果还是发现磁盘空间不足。

1、先描述一下已有的场景。之前创建了zfs的一个pool:data。并且通过
zfs create data/mysqldata
创建了mysql数据的目录
在5月27号有一个快照。那么数据库没有启动时,可以看到的如下:
[root@xy-offer-db13a:/data]#zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 49.8G 124G 2.90M /data
data/mysqldata 49.7G 124G 49.7G /data/mysqldata
data/mysqldata@100527 0 – 49.7G –
此时的磁盘空间占用情况
[root@xy-offer-db13a:/data]#cd /data/
[root@xy-offer-db13a:/data]#du -sh *
48G mysqldata
2.9M slow-query.log.20100418
mysqldata文件占用了48G。slow-query占用了一点点。我们不用管它。快照因为数据没有不一致,所以占用的空间为0。此时mysqldata对应recordsize为zfs默认的128k。

2、我们想要做的是,把mysqldata里面的文件先mv出来,放到data文件系统中。然后修改mysqldata的recordsize为16k,然后cp进来,删除data文件系统中的备份文件

3、实际执行:
第一步:我们把数据mv到data文件系统
提交执行,mv命令:
[root@xy-offer-db13a:/data]#mkdir /data/md
[root@xy-offer-db13a:/data]#/bin/mv -f /data/mysqldata/* /data/md/
此时看到的磁盘容量变化:
[root@xy-offer-db13a:/data]#cd /data/
[root@xy-offer-db13a:/data]#du -sh *
1.5G md
45G mysqldata
2.9M slow-query.log.20100418
[root@xy-offer-db13a:/data]#df -kh
Filesystem size used avail capacity Mounted on
/dev/dsk/c0t0d0s0 19G 5.2G 14G 28% /
/devices 0K 0K 0K 0% /devices
ctfs 0K 0K 0K 0% /system/contract
proc 0K 0K 0K 0% /proc
mnttab 0K 0K 0K 0% /etc/mnttab
swap 8.5G 980K 8.5G 1% /etc/svc/volatile
objfs 0K 0K 0K 0% /system/object
sharefs 0K 0K 0K 0% /etc/dfs/sharetab
/usr/lib/libc/libc_hwcap1.so.1
19G 5.2G 14G 28% /lib/libc.so.1
fd 0K 0K 0K 0% /dev/fd
swap 8.5G 24K 8.5G 1% /tmp
swap 8.5G 24K 8.5G 1% /var/run
/dev/dsk/c0t0d0s3 46G 156M 45G 1% /export/home
data 174G 3.1G 121G 3% /data
data/mysqldata 174G 47G 121G 28% /data/mysqldata
[root@xy-offer-db13a:/data]#zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 52.8G 121G 3.06G /data
data/mysqldata 49.7G 121G 47.1G /data/mysqldata
data/mysqldata@100527 2.56G – 49.7G –
我们会发现快照占用的空间一直在涨,
[root@xy-offer-db13a:/data]#zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 83.0G 91.3G 33.2G /data
data/mysqldata 49.7G 91.3G 26.0G /data/mysqldata
data/mysqldata@100527 23.7G – 49.7G –
一直到mv完成了,完成mv以后磁盘占用情况如下:
[root@xy-offer-db13a:/data/md]#zfs list
NAME USED AVAIL REFER MOUNTPOINT
data 116G 58.5G 65.9G /data
data/mysqldata 49.7G 58.5G 1.81G /data/mysqldata
data/mysqldata@100527 47.9G – 49.7G –
快照占用了47.9G(REFER),data/mysqldata里面已经没有任何数据了,还占用了49.7G(对应的就是快照的REFER),跟你没有把文件mv出去之前是一模一样的。也就是说,虽然你把文件mv出去了,如果快照还在那边的话,磁盘空间你一样的还占用了那么多。
其实这个也是可以理解的,你需要回复到之前有数据的样子,当然需要保存这么多数据阿。
这里还有一个问题,48G的MySQL数据mv到data文件目录下来,变成了65G。难道是由于recordsize改变了,导致文件增大了?
专门验证了一下,在另外一台机器上,数据只有47G
[root@xy-offer-db14a:/data/mysqldata]#du -sh
47G .
我们把它mv到data文件系统
[root@xy-offer-db14a:/data/mysqldata]#mkdir /data/md
[root@xy-offer-db14a:/data/mysqldata]#/bin/mv -f /data/mysqldata/* /data/md/
[root@xy-offer-db14a:/data/md]#du -sh
64G .
[root@xy-offer-db14a:/data/md]#zpool list
NAME SIZE USED AVAIL CAP HEALTH ALTROOT
data 177G 111G 66.3G 62% ONLINE –
变成了64G。这个zfs的compress参数都是设置为off的。
[root@xy-offer-db14a:/data/md]#zfs get compress
NAME PROPERTY VALUE SOURCE
data compression off default
data/mysqldata compression off local
data/mysqldata@100527 compression – –
为什么拷贝一下,在同一个pool里面,会从47G变成64G?
[root@xy-offer-db14a:/data/mysqldata]#du -sh *
2.2G binlog
3.0G innodb_log
222M innodb_ts
41G mydata
287M relaylog
1K tmpdir
[root@xy-offer-db14a:/data/md]#du -sh *
2.2G binlog
3.0G innodb_log
2.0G innodb_ts
56G mydata
287M relaylog
1K tmpdir
这里innodb_ts从原来的222M增长为2G,可以理解,是MySQL虽然分配的时候是2G,但是实际用到的只有200多M,mv的过程中把空间都占用上了。mydata目录平白无故的涨了15G。难道也是这样的原因?有点想不通。

第二步,将数据拷贝回去
这里由于第一次做没有注意到前面空间的变化,所以拷贝数据回data/mysqldata目录时,空间已经不足了。

4、解决方案:
将数据mv出去,同样可以将数据mv进来,咨询了老唐,认为mv同样能够达到把文件修改为16k recordsize的效果。(如果只是修改文件指针,而不是真正的拷贝复制文件,mv应该很快返回,而现在确实需要近半个小时才能完成mv操作。并且/data和/data/mysqldata在不同的mout点上)。

这里学到了一个事情:移动zfs文件的时候,你需要计算快照可能引起的空间成倍增长。
修改zfs的recordsize是否会让文件占用空间增长还需要进一步验证。

may your success.

Statement is not safe to log in statement format

今天收到MySQL的报警:
100607 20:55:23 [Warning] Statement is not safe to log in statement format. Statement: update users set Status = ‘6’ , gmt_modified = now() where ecpnumber = ‘057181933005’ limit 1
以前没有遇到过,觉得非常奇怪。仔细一看,update limit 1。
MySQL的limit 1返回的1行数据并不能保证下次执行返回的还是同一行,估计MySQL就是由于这个原因报警出来。提醒我们:
update users set Status = ‘6’ , gmt_modified = now() where ecpnumber = ‘057181933005’ limit 1在主机执行,并以statement记录到本机的话是不安全的。为什么列,因为它是以statement方式记录下来的。以这个方式记录下来的binlog在备机上执行将同样经过SQL解析等步骤和我们手工提交到备机的情况基本一样。这样的话,备机更新的这条数据可能不是主机更新的那条数据。结果导致主备机数据不一致。
检查了数据库的设置,果然binlog_format为STATEMENT。
问题修复
1、对已经发生的问题修复:检查发现where ecpnumber = ‘057181933005’的条件对应的数据只有一条,也就是说,limit 1和不加limit 1都是同一个数据
2、避免新问题的发生。
a)跟应用方确认,请他们修改掉limit 1的这种方式。通过select查询到底有几条,到底需要更新哪个数据,从而确切的修改那一条数据。
b)修改MySQL数据库参数,数据库这边的修改很简单。set global binlog_format=mix;

may you success