主备备的两个备机转为双Master出现诡异的slave lag问题

  • 996 views
Uploaded on

Mysql replication方面的

Mysql replication方面的

More in: Education
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
996
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
7
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. 主备备的两个备机转为双 Master 出现诡异的 slave lag 问题有三台 MySQL 服务器,a,b 和 c,复制关系为 a -> b -> c。a,b,c 的 server_id 分别为 1,2,3因为需要切换为 a b <-> c,也就是说,a 单独出来,b 和 c 作为双 master 结构时。这种切换会经常出现在需要搭建备机把数据备份出来,然后把 a 独立出来的 case 中。昨天,我就做了这样的切换,结果发现出现莫名奇妙的 slave lag。Seconds_Behind_Master 一下子为 0,一下子变成几千秒。使用 mysqlbinlog 查看,binlog 日志里面也有很多时间在几小时以前的 event 数据。为了验证复制是否正常,我特别测试了一下,在 b 建一个表,并插入时间数据,到 c 上一看,表已经复制过来了,时间数据也是正确。询问了一下同事,他说应该是 MySQL 的 bug,在这种切换的情况下很容易触发这个 bug,可以采用 stop slave;change master; start slave;的方法来修复。但是实际的数据其实完全没有影响,复制还是正常的。于是我按照这个办法:stop slave io_thread;stop slave;show slave statusG(这里先停 io_thread 是为了 SQL thread 和 IO thread 都执行到了同一个位置,
  • 2. change master 的时候没有风险)stop slave;change master to … ; start slave;(change master 到 show slave status 的 Master_Log_File:和 Exec_Master_Log_Pos:位置,也就是说,其实根本没有改变复制的位置)结果 slave lag 依然故我。这个问题就比较郁闷了。时间已经过了午夜,脑袋也转不动了,想过不管它了,反正复制没有问题。但是问题没有解决总觉得什么东西卡在喉咙一样。各种资料,各种变量都参考了一遍,最后,基本不太意识的输入:show master logs;show binlog events in ‘mysql-bin.000680′ from 34385301;想看看最新产生的 event,结果就发现不对的地方了。这个最新产生的 event 有很多,并且 server_id 是 1,1 是 a 的 server_id 啊,应用访问的是 b 啊,怎么会在 b 上面产生 a 的 server_id 列,MySQL 哪里出问题了?仔细一想,明白了,事情是这样的:a -> b -> c,a 的 event1(server_id 为 1)复制到 b,也会复制到 c,这个是正常的。然后搭建 c -> b 的复制关系时,b 需要断开 a 的连接,切换主库到 c,在 change master 的位置在 event1 出现之前,那么 event1 肯定会被重新复制到 b 去,event1 的 server_id 是 1,那么 b 判断,这个 event1 不是我提交的,需要在本地执行,并且把它记录到了自己的 binlog 中;由于 b 和 c 是双 master 结构,event1 又复制到了 c,c 同样判断它不是我提交
  • 3. 的,那么我需要在本地执行,并且记录到本地 binlog 中。这样 event1 就在 b 和 c 之间循环往复,时间保持不变,MySQL 的 slave lag 也就一下子是 0,一下子是几千秒了。这里,还需要说明一点,在环型复制里面,event 之所以能够在环内只循环一次,而不是重复做,是因为提交的那个节点会发现这个 event 的 server_id 是自己的server_id,也就是说是自己提交的。那么,它就不会把这个 event 再应用一次,自然也不会记录到 binlog。这个循环就结束了。除非你闲着没事做,设置了 replicate-same-server-id 参数。那么解决问题怎么办列,很简单,把没有应用访问的 c 的 server_id 设置成 a 的server_id:set global server_id=1;看看时间差不多了,server_id 为 1 的 event 都被干掉以后:set global server_id=3;然后再设置回来。还好,MySQL 5.0 和 5.1 的 server_id 都是动态的。may your success.research report for MySQL multi-master tool把老的多主 master 向同一个 slave 复制的文档找出来了。这个是研究性质的,不会牵涉到太多技术细节,所以不担心会有泄漏以前公司技术的嫌疑。之前公司的这个软件已经完成了,包括事件解析,应用以及冲突处理。我自己想做一个开
  • 4. 源的,multi_master 的版本,但是架构基本上会非常不同。希望有时间能够真正的做出来阿,这个功能相信对我们还是非常有用的。Multi-Master测试报告目 录目 录21 报告信息 32 概述 42.1 目的 42.2 相关信息 42.3 MySQL replication 数据复制格式 53 测试过程 93.1 测试进度安排 93.2 测试过程描述 94 测试结论 124.1 测试最终结论 125 附录 135.1 附录 1 MySQL 5.1.20 Beta 包含的事件类型 135.2 附录 2 MySQL 5.1.20 Beta 各事件的附加事件头长度 14
  • 5. 5.3 附录 3 MySQL 5.1.20 Beta 中各列在内部存储时可能的各种数据类型 165.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 中复制和同步数据,并且在将来一段时间也不准备增
  • 6. 加这个功能。所以我们打算先对 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 端创建的用于读取中继日志并执行日志中语句的线程。如果有多个从服务器,主服务器将为每个当前连接的从服务器创建一个(BinlogDump)线程;每个从服务器都有自己的 I/O 和 SQL 线程。
  • 7. MySQL 复制基于服务器在二进制日志中对所有数据库的更改(更新、删除等等)。因此,要进行复制,必须在主服务器上启用二进制日志。从 MySQL 5.1 开始,MySQL replication 可以支持两种复制类型,第一种是原有的 statement based replication,也就是将在 master 端提交的查询语句及相关环境一起记录到 binlog 中,slave 模拟该环境并提交语句执行。第二种是 rowbased 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-basedreplication 是 MySQL 5.1 版本才被引入的。下面我们所涉及的复制除了特殊指明外都是指 row based replication。在 MySQL 5.1.20 Beta 版本中,在 master 端如果一条语句改变了一个表的多条数据,master 将首先在 binlog 中记录下一个 Tablemap 事件用于将表的相关信息以及表的 id 信息对应记录下来。而针对每一行需要改变的数据,master端单独记录一个 writerow(updaterow,deleterow)事件,而且每一行数据都是二进制的 MySQL 内部格式的数据。其中 writerow 对应的查 询语句是:inser
  • 8. t 和 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 参数中依序
  • 9. 填入下述的各个字段,并指定其中的参数 command 为 COM_REGISTER_SLAVE 以注册自 己。名称 字节数 含义server_id 4 本 MySQL instance 的 server_id 值strlen(report_hos 1 or 2 标识接下来的 report_host 的长度,如果长t) 度<251 占 1 个字节,否则占两个字节report_host Strlen(report_hos 向主服务器注册的 MySQL instance 标识 t)strlen(report_use 1 or 2 标识接下来的 report_user 的长度,如果长r) 度<251 占 1 个字节,否则占 2 个字节report_user Strlen(report_use 向主服务器注册的用户名 r)strlen(report_pas 1 or 2 标识接下来的 report_password 的长度,如sword) 果长度<251 占 1 个字节,否则占 2 个字节report_password Strlen(report_pas 向主服务器注册的密码 sword)report_port 2 向主服务器注册的端口rpl_recovery_ran 4 复制的恢复等级kmaster_id 4 填入 0,主服务器将自行填入 master_id 值
  • 10. register_master图 1、主服务器注册示意图向主服务器请求数据从 服务器向主服务器发送了请求数据的命令以后主服务器将根据要求将对应 binlog 文件的指定位置开始的事件记录发送给从服务器。向主服务器请求数据,可以 调用 mysql.h 中的 simple_command(mysql, command, arg, length, skip_check)函数,在 arg 参数中依序填入下述的各个字段,并指定其中的参数 command 为 COM_BINLOG_DUMP。名称 字节数 含义master_log_pos 4 请求主服务器发送的事件记录在 bin log 文件中的偏移量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()。
  • 11. 向 主服务器请求了数据以后,从服务器就可以通过 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 BIN_LOG_HEADER_SI Binlog 文件的标识值(即”xfex62x69x ZE(4)6e”)MySQL SQL thread 数据格式只 要循环的调用 cli_safe_read 函数,从服务器可以不断得到从主服务器发送过来的事件记录。接下来我们介绍一下相关的一些事件记录格式。在提交了 COM_BINLOG_DUMP 命令后,主服务器首先给从服务器发送的两个事件依序分别为ROTATE_EVENT 和 FORMAT_DESCRIPTION_EVENT 事件。ROTATE_EVENT 事件用来标示接下来主服务器将从哪一个 binlog 文件的哪个位置开始 发送事件记录。而 FORMAT_DESCRIPTION_EVENT 事件用来记录本 MySQL inst
  • 12. ance 的 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_writte 4 该事件一共占用的字节数,包括事件头的字节数。nlog_pos 4 下一事件在 binlog 文件中将要开始的位置,即本事件的结束位 置Flags 2 事件的其他标志位。ROTATE_EVENT 事件字段描述由于各个事件的事件头基本一致,这里我们就不重复介绍事件头的各字段了,后面的各个事件我们也都将忽略对事件头字段的描述。
  • 13. ROTATE_EVENT 事件的附加事件头字段主要包括:名称 字 节 含义 数pos 8 主服务器将要发送的事件记录在 binlog 文件中的偏移量。一般 为从服务器提交的 COM_BINLOG_DUMP 请求中的偏移量值。ROTATE_EVENT 事件的其他信息字段主要包括:名称 字节数 含义new_log_i strlen(new_log_ 主服务器将要发送的事件记录的 binlog 文件名。一dent ident) 般为从服务器提交的 COM_BINLOG_DUMP 请求中 的 binlog 文件名。FORMAT_DESCRIPTION_EVENT 事件字段描述FORMAT_DESCRIPTION_EVENT 事件的附加事件头的字段如下:名称 字节数 含义binlog_versi 2 Binlog 文件的版本号,这里一般为最新的版on 本号 4server_versi ST_SERVER_VER_L MySQL 的版本号。例如: 5.1.20-beta-log” ”on EN(50)Created 4 事件创建时间,这里一般和事件头中的 wh en 一致
  • 14. event_head 1 一般事件的事件头长度,一般设置为:LOGer_len _EVENT_HEADER_LEN(19)post_header ENUM_END_EVENT- 不同事件类型的附加事件头的长度,见附录_len 1(26) 2。TABLE_MAP_EVENT 事件字段描述TABLE_MAP_EVENT 事件的附加事件头的字段如下:名称 字节数 含义m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符m_flags 2 表的各种标志位,见附录 4TABLE_MAP_EVENT 事件的其他信息字段主要包括:名称 字节数 含义m_dbl 1 数据库名的长度enm_dbn m_dblen+1 数据库名,以’0’结尾amm_tblle 1 表名的长度nm_tbln m_tbllen+1 表名,以’0’结尾am
  • 15. m_colc net_field_len 表的字段个数,所占字节数根据第一个字节的大小由 net_fnt gth() ield_length 函数确定m_colt m_colcnt 表的各个字段的字段类型,参见附录 3。ypeWRITE_ROWS_EVENT 事件字段描述WRITE_ROWS_EVENT 事件的附加事件头的字段如下:名称 字节数 含义m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符m_flags 2 表的各种标志位,见附录 4WRITE_ROWS_EVENT 事件的其他信息字段主要包括:名称 字节数 含义m_width net_field_length() 表的各列的位图长度,所占字节数根据第一个字节 的大小由 net_field_length 函数确定m_cols.bit (m_width + 7) / 表的各列的位图,每一位表示 m_rows_buf 是否包map 8 含表中一列的值,如果没有置位表示该列的值没有 包含在 m_rows_buf 中m_rows_b 剩余字节数(len- 将要插入到表中的一行数据值。uf 已占字节数)
  • 16. UPDATE_ROWS_EVENT 事件字段描述UPDATE_ROWS_EVENT 事件的附加事件头的字段如下:名称 字节数 含义m_table_id 6(5.1.4 前的版本中为 4) 表的 id 标识符m_flags 2 表的各种标志位,见附录 4UPDATE_ROWS_EVENT 事件的其他信息字段主要包括:名称 字节数 含义m_width net_field_length() 表的各列的位图长度,所占字节数根据第一 个字节的大小由 net_field_length 函数确定m_cols.bitm (m_width + 7) / 8 表中被匹配行数据的各列的位图,每一位表ap 示 m_rows_buf 是否包含表中该列的值。m_cols_ai.bi (m_width + 7) / 8 表中将要更新的行数据的各列的位图,每一tmap 位表示 m_rows_buf 是否包含表中一列的 值。m_rows_buf 剩余字节数(len-已占字 表中被匹配的那一行数据的值以及将要更 节数) 新的一行数据值。DELETE_ROWS_EVENT 事件字段描述DELETE_ROWS_EVENT 事件的附加事件头的字段如下:
  • 17. 名称 字节数 含义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.bit (m_width + 7) / 表的各列的位图,每一位表示 m_rows_buf 是否包map 8 含表中一列的值。m_rows_b 剩余字节数(len- 表中将要删除的一行数据值。uf 已占字节数)XID_EVENT 事件字段描述XID_EVENT 一般出现在一个事务操作(transaction)之后或者其他语句提交之后。它的主要作用是提交事务操作和把事件刷新至 binlog 文件中。XID_EVENT 事件的信息字段包括:名称 字节数 含义xid sizeof(xid) 8 commit 标识符
  • 18. 测试过程测试进度安排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)复制执行空语句之间的区别。
  • 19. 测试过程描述通过对 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,而不是我们普通提交查询
  • 20. 时记录的本地的 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 操作的提交和复制过程。
  • 21. 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 语句最开
  • 22. 始就是在本机提交执行的,它将忽略该 update 语句。针对这个问题,我们有两种解决方案:1. 1. 1. 由于在 MySQL server 中,update 要更新的那一行数据匹配不成 功那么对于 MySQL 数据库的将不会有任何修改,并且这一条 upd ate 语句也将不会记录到 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 数
  • 23. 据前有其他的语句 insert 或者 update 生成了若干行能够被匹配的 数据的话,这种方案是行不通的。1. 1. 1. 第二种解决方案相对复杂一些。它保证了 update 语句更新行数据 的 server_id 值为本 MySQL server 的 server_id 值。我们利用了 MySQL 提供的 trigger, update 语句提交执行之前改变行数据 N 在 EW 的 server_id 值为本 MySQL server 的 server_id 值。(在 My SQL 的 trigger 中,OLD 行数据表示数据库中已有的将要被 updat e 匹配的数据,而 NEW 行数据 表示 update 语句将要更新为其值 的行数据)。但是,由于我们的 multi-master 工具将生成的语句直 接提交到本 MySQL instance 中执行,那么如果我们的 trigger 不 能识别工具提交的语句和用户提交的语句,而把 NEW 行数据的 s erver_id 值全部改成本 instance 的值,复制的死循环一定会出现。 例如:在上面的例子中,M1 的 server_id 为 1,在 M2 上 update 的数据复制到 M1 上提交执 行,如果 trigger 不能识别出它是从 m ulti-master 工具生成的语句,而是把它的 NEW 行数据修改为 1, 那么记录在 M1 的 binlog 中的数 据将不能标识最开始提交语句的 MySQL instance 位置,从而不能中断数据复制的循环。至少有两 种方法可以区分普通的用户提交语句和 multi-master 工具提交的 语句。 专门指定 一个用户用于 multi-master 工具提交查询语句, 1, 以区别于其他用户提交的语句。在 trigger 中我们可以用 user()
  • 24. 函数获得用户名来确定查 询语句提交的来源。2,通过 multi-mas ter 修改相应语句而且使得它提交的所有语句与普通用户提交的语 句不同。下面我们详细阐述第二种策略。我 们发现用户提交的所有 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 foreach rowBEGINIF NEW.server_id=OLD.server_id THENSET NEW.server_id=1;ELSEIF NEW.server_id=0 THENSET NEW.server_id=OLD.server_id;END IF;END;//delimiter ; 1. Delete 的提交和复制:
  • 25. 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
  • 26. {/*Every time you update this enum (when you add a type), you have tofix 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 longersql_ex, allowing multibyte TERMINATED BY etc; both types share the
  • 27. 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 aretherefore 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*/
  • 28. 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 theirnumbers*/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
  • 29. #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
  • 30. 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 changedthe table map id to be stored in 6 bytes: when it was stored in 4
  • 31. bytes (=> post_header_len was 6). This is used to test backwardcompatibility.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 (checkwith 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
  • 32. {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_mediumMYSQL_TYPE_DATE = 10,MYSQL_TYPE_TIME = 11,MYSQL_TYPE_DATETIME = 12,MYSQL_TYPE_YEAR = 13,MYSQL_TYPE_NEWDATE = 14,MYSQL_TYPE_VARCHAR = 15, //field_varstringMYSQL_TYPE_BIT = 16,MYSQL_TYPE_NEWDECIMAL = 246,MYSQL_TYPE_ENUM = 247,
  • 33. 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
  • 34. appropriate flag set using the normal bitwise operators. Theimplicit conversion from an enum-constant to an integer isaccepted by the compiler, which is then used to set the real setof 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{
  • 35. 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。下面是我的测试描述和结果。
  • 36. 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 为 statementroot@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 |
  • 37. +——————-+———–+2 rows in set (0.00 sec)3、主机上创建测试表:root@localhost : (none) 10:00:02> use alitest;Database changedroot@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)
  • 38. 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:
  • 39. 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、备机上设置为 statementroot@localhost : (none) 10:06:44> set global binlog_format=’statement’;Query OK, 0 rows affected (0.00 sec)
  • 40. 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 |+——————+———–+————+———–+————-+—————————————————————+
  • 41. | 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)
  • 42. 测试 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、备机上设置为 statementroot@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)
  • 43. 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 */ |
  • 44. | 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.5Command-Line Format –binlog-formatConfig-File Format binlog-formatOption Sets Variable Yes, binlog_formatVariable Name binlog_formatVariable Scope BothDynamic Variable Yes
  • 45. Permitted Values (>= 5.1.5, <= 5.1.7)Type enumerationDefault STATEMENTValid Values ROW, STATEMENTPermitted Values (>= 5.1.8, <= 5.1.11)Type enumerationDefault STATEMENTValid Values ROW, STATEMENT, MIXEDPermitted Values (>= 5.1.12, <= 5.1.28)Type enumerationDefault MIXEDValid Values ROW, STATEMENT, MIXEDPermitted Values (>= 5.1.29)Type enumerationDefault STATEMENTValid Values ROW, STATEMENT, MIXEDSpecify 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
  • 46. Setting the binary logging format without enabling binary logging prevents the MySQL server from starting. This is a known issue in MySQL 5.1which 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.eshop MySQL 数据库主备机数据 xtrabackup 同步数据今天需要再次用到 xtrabackup 来备份和恢复数据。找了半天终于把以前写的一个文档找到了。保存在 blog 中。以防丢失。xtrabackup 更换为 xtrabackup-1.2-22.rhel5.x86_64.rpm 也已经测试通过一.迁移目的目前 ITBU eshop MySQL 数据库有 64 台数据库服务器。两两互备作为双 master 结构相互备份。但是由于应用方使用了 MySQL 的 uuid 函数,导致两两互备的 mysql 服务器之间的数据不一致。这样两个数据库切换将导致用户数据的丢失。4 月底,应用方修改了 uuid 的问题,避免的新的数据不一致问题。接下来就只有数据库中已有的数据不一致问题了。MySQL 数据库,由于主备之间已经切换过多次,目前一部分数据已经无法找回。
  • 47. 跟应用沟通后,确定以目前主库的数据为准,将主库的数据同步到备库。备库的数据备份到本机。保存一个月。二.迁移要求1、迁移过程中要求不影响应用,也就是说 eshop 数据库服务不能停止。2、迁移完成后主备机数据保持一致。三.迁移方案这里由于管理维护的必要,我们假设主备机我们都是以 root 登录。xtrabackup在 MySQL 中的权限为:root@127.0.0.1 : (none) 20:57:24> show grants for ‘xtrabackup’@localhost’G*************************** 1. row ***************************Grants for xtrabackup@localhost: GRANT SELECT, RELOAD, LOCK TABLES, REPLICATION CLIENT ON *.* TO ‘xtrabackup’@localhost’ IDENTIFIED BY PASSWORD ‘*BF9F4C1B8BC37C75BBF482C8037EC944555D371A’*************************** 2. row ***************************Grants for xtrabackup@localhost: GRANT INSERT, CREATE, DROP ON`mysql`.`ibbackup_binlog_marker` TO ‘xtrabackup’@localhost’2 rows in set (0.00 sec)3.1.备份/恢复工具选择由于迁移过程中不能够停止数据库服务,数据库备份必须选择热备份。两种选择
  • 48. mysqldump 的逻辑热备以及 xtrabackup 的物理热备。为了恢复时间考虑,决定采用 xtrabackup。3.2.备机备份数据的选择eshop 网店目前有两个存储,他们目前都在老聚园路。一个存储暂时没有上电,另外一个存储空间所剩不多。目前每台 eshop 备机平均为 130G 的数据,一共 32 台,需要 4T 多的存储空间。而如果把 eshop 的 32 套备机数据放到存储上,对存储空间和网络的消耗都是巨大的。最终,我们选择在备机存储本机的老数据。而不做另外拷贝。附件 eshop_backup_slave_data_20100422.sh 是备份备机数据的脚本3.3.主备机数据拷贝方案选择主机备份生成的数据大小约为 130G 左右,正常的话我们可以通过 scp 来拷贝,但是 scp 拷贝要么需要手工输入密码,要么需要打通 ssh 隧道。如果用脚本来操作的话,只能打通主备机之间的 ssh 隧道。另外 scp 不能够限速。数据拷贝量大的话可能引起网络堵塞。最后,考虑采用 rsync 拷贝数据,它不仅能够限速。并且可以在主机上启动 rsync –daemon,不用打通隧道(需要注意在拷贝完数据以后停止 rsync 后台进程)附件 eshop_master_rsync_daemon_local.sh 和 eshop_master_rsync_daemon_remote.sh 是用于在 eshop 的 master 机器上添加 rsync daemon 的脚本。rsyncd.conf 是拷贝到主机的 rsync 配置文件。3.4.主机数据库备份方案eshop 主备机每天 5: 的时候都会有一个计划任务判断当前主机是否是备机。 30如果是备机则利用 xtrabackup innobackupex-1.5.1 版本为 0.7) ( 生成物理备份。
  • 49. 这里我们只需要简单修改一下备份脚本就可以在主机上也生成备份(注意主机备机完之后需要把该脚本修改回来)。直接利用innobackupex-1.5.1 –slave-info –no-timestamp –user=”${XTRA_BACKUP_USER}” —password=”${XTRA_BACKUP_PASSWORD}” ${XTRA_BACKUP_DIR}生成备份。这里需要注意 xtrabackup 用户的权限。附件 eshop_master_backup_local.sh 是用于主机 xtrabackup 备份数据库的脚本。backup_mysql.sh 是需要拷贝到主机上执行的脚本3.5.备机数据恢复方案主机数据拷贝到备机以后,由于是 xtrabackup 备份出来的,所以也要用它来恢复。注意,在恢复备机数据库数据前需要将备机数据库 MySQL 先停掉。原有的MySQL 数据目录需要 mv 到另外的位置。并根据需要新建好数据库需要的目录,为备份数据拷贝到对应目录准备好环境。xtrabackup 需要先生成 logfile,然后将产生的相关数据拷贝到具体的数据文件目录中。需要需要执行两次。■第一次利用:innobackupex-1.5.1 —apply-log /home/mysql/fs3/master_bak_data/eshop_alsmy_eshop13b_2010_04_27/应用备份期间变化的数据和生成 ib_logfile。这里需要注意将该命令输出的结果记下来。后面主备机双 maser 复制环境的搭建需要利用到这里的”Last MySQLbinlog file position”信息■第二次利用:
  • 50. innobackupex-1.5.1 —copy-back /home/mysql/fs3/master_bak_data/eshop_alsmy_eshop13b_2010_04_27/将生成好的数据拷贝到/etc/my.cnf 指定的 datadir 和 innodb_data_home_dir 等对应目录中。3.6.备机数据库启动和清理备机数据库数据恢复以后,需要将数据库的目录 owner 修改一下。chown -Rmysql:mysql ${MYSQL_DATA_DIR}。否则 MySQL 启动会出错。启动 MySQL 数据库,正常的话启动是没有问题的。如果出现问题,需要查看 MySQL 的错误日志,并根据不同的错误类型处理。启动成功以后,MySQL 数据库里面的数据都是主机的数据。我们需要根据不同的情况对它进行修改。(这里最好 SET SQL_LOG_BIN=0 避免把一些数据记录到 binlog 中,从而复制到主机去了。)eshop 这边因为复制帐号是 replicator@’${SLAVE}’基于备机 ip 地址的,所以需要修改为基于主机的复制帐号。这样备机到主机的复制才能搭建起来。root@127.0.0.1 : (none) 16:13:06> show grants for replicator@’172.18.94.25′;+—————————————————————————————————————————————————————————–+| Grants for replicator@172.18.94.25 |+—————————————————————————————————————————————————————————–+| GRANT SELECT, RELOAD, SUPER, REPLICATION SLAVE, REPLICAT
  • 51. ION CLIENT ON *.* TO ‘replicator’@’172.18.94.25′ IDENTIFIED BY PASSWORD ‘*3836CCEA58805DB4CE7093BF6170F7A6027CDD86′ |+—————————————————————————————————————————————————————————–+1 row in set (0.00 sec)root@127.0.0.1 : (none) 16:14:20> GRANT SELECT, RELOAD, SUPER,REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO ‘replicator’@’172.18.94.26′ IDENTIFIED BY ‘xlia9810pal’;Query OK, 0 rows affected (0.00 sec)root@127.0.0.1 : (none) 21:21:02> drop user replicator@’172.18.94.25′;Query OK, 0 rows affected (0.03 sec)root@127.0.0.1 : (none) 16:14:44> flush privileges;Query OK, 0 rows affected (0.00 sec)附件 eshop_slave_sync_master_data.sh 是用来恢复备机数据库和清理数据的脚本。3.7.主备机双 master 复制环境搭建3.7.1.搭建备机到主机的复制由于备机是从主机导入的数据,并且备机没有应用访问,binlog 没有变化。我们首先搭建备机到主机的复制。首先确定备机的 binlog 位置。利用 show masterstatus 可以查到。root@127.0.0.1 : (none) 21:37:38> show master status;+——————+———–+————–+——————+
  • 52. | File | Position | Binlog_Do_DB | Binlog_Ignore_DB |+——————+———–+————–+——————+| mysql-bin.000001 | 98 | | |+——————+———–+————–+——————+1 row in set (0.00 sec)如无意外,都应该是第一个文件的最开始位置。修改主机的 master 位置:Stop slave;CHANGE MASTER TO MASTER_HOST=’172.18.94.25′, MASTER_USER=’replicator’, MASTER_PASSWORD=’xlia9810pal’, MASTER_LOG_FILE=’mysql-bin.000001′, MASTER_LOG_POS=98;Start slave;检查是否复制搭建成功:Show slave statusG如果 Slave_IO_Running,Slave_SQL_Running 其中一个为 No。需要检查 MySQL 的错误日志。一般可能有两种错误:■备机的授权有问题■备机的 binlog 位置有问题附件 eshop_master_change_master.sh 为配置备机到主机的复制环境的脚本。3.7.2.搭建主机到备机的复制保证备机到主机的复制正确以后,我们就可以搭建主机到备机的复制了。备机从主机的哪个位置开始复制很重要,这个位置是从 备机数据恢复方案 中得
  • 53. 到的。在 apply-log 的时候,有一行文字很重要:InnoDB: Last MySQL binlog file position 0 651339984, file name ./mysql-bin.001604这里记录了主机的复制开始文件和开始位置(这里就是 mysql-bin.001604,651339984)。那么我们搭建主机到备机的复制就很简单了:Stop slave;CHANGE MASTER TO MASTER_HOST=’172.18.94.26′, MASTER_USER=’replicator’, MASTER_PASSWORD=’xlia9810pal’, MASTER_LOG_FILE=’mysql-bin.001604′, MASTER_LOG_POS=651339984;Start slave;检查是否复制搭建成功:Show slave statusG如果 Slave_IO_Running,Slave_SQL_Running 其中一个为 No。需要检查 MySQL 的错误日志。这里由于备份的时间已经经过了这么长的时间,主机到备机的复制肯定有延迟,静静的等待主备机的复制就可以了。附件 eshop_slave_change_master.sh 为配置主机到备机的复制环境的脚本。四.附件介绍附件中的各个脚本介绍如下:■附件 eshop_backup_slave_data_20100422.sh 是备份备机数据的脚本■附件 eshop_master_backup_local.sh 是用于主机 xtrabackup 备份数据库的脚本。backup_mysql.sh 是需要拷贝到主机上执行的脚本
  • 54. ■附件 eshop_master_rsync_daemon_local.sh 和 eshop_master_rsync_daemon_remote.sh 是用于在 eshop 的 master 机器上添加 rsync daemon 的脚本。rsyncd.conf 是拷贝到主机的 rsync 配置文件。■附件 eshop_slave_sync_master_data.sh 是用来恢复备机数据库和清理数据的脚本。■附件 eshop_master_change_master.sh 为配置备机到主机的复制环境的脚本。■附件 eshop_slave_change_master.sh 为配置主机到备机的复制环境的脚本。may you success.MySQL 数据库机器搬迁 checklistitbu 的数据库机器从老机房搬迁到新机房的项目已经接近尾声了,这次的搬迁是我经历的一个比较大的项目,虽然整体的调控不是我主导的,但是 MySQL 数据库的搬迁都是我在主导进行,期间出现了一些问题,还好没有出现非常重大的错误。从中相信搬迁的大部分同学都成长了很多。我自己感觉我学到了很多,也成长了很多,虽然加班加点的,累的够呛,还是很值得的。为了避免以后大家再走弯路,也避免自己忘记搬迁的重要事项,特别记录下来,做为一个搬迁工程的 checklist。方便大家搬迁过程中检查,不要遗漏了一些东西。1、确认方案。尽早和应用方沟通,确认采用平滑搬迁或者停机搬迁的方案。平滑搬迁是指 MySQL 数据库停止备机,然后搬迁过去,在新环境搭好以后,配置 MySQL 的双 master 复制,调试通过,应用服务正常。然后直接刷 dns(或者其他方式)使应用的真实用户访问新的服务。停机搬迁的方式是指,应用发布停机公告,在适当的时机,将应用服务器和数据
  • 55. 库服务器等搬迁到新机房,部署上线。上面的两种方案,第一种风险较小,对用户影响也非常小。万一刷过去以后,真实用户访问有问题,还可以切回来。第二种风险比较大,甚至可能出现有些配置修改修改不及时,造成停机时间已经过了,应用无法访问的问题。2、确认机架位置。正常的话,sa 负责人会将搬迁的机器列表和机柜,机架位置发给大家一份。以方便现场工程师以及对应人员确认搬迁的就是这批机器。我们需要注意一下,并提醒现场工程师机柜和机架号。eshop 搬迁的时候,我们的一个现场工程师就看错了机柜号,拔掉了一台正在提供应用服务的机器,还好立刻意识到了,大家一起快速修复了这个问题。3、确认搬迁的机器 IP 和搬迁到新机房的机器 IP。老机房需要搬迁的机器 IP 在上一步就需要确认好了,新机房的机器 IP 需要跟 sa 和网络部门的同事沟通,我们提供具体的分配策略,网段划分,新老机器 IP 对应关系。4、确认时间。及早跟各应用方沟通搬迁时间,如果有变化,及时相互通知。5、搬迁前准备工作。eshop 这边搬迁前需要重新同步主机数据到备机。其他应用需要注意主备机的复制延迟,尽量保证复制延迟较低。6、通知所有相关人员。包括数据仓库,应用开发人员,网络,sa。告知他们新的数据库 IP 地址和连接方式。另外还需要检查 MySQL 里面的所有账户,看这些用户是否都通知到了。pm 就没有及时通知数据仓库人员,导致第二天他们取不到数据的问题。7、修改搬迁以后的配置。包括 MySQL 账户的修改,heartbeat 的修改,cobar的修改,脚本的修改,脚本的配置文件修改,带外登录等。a)MySQL 帐号的修改。检查 MySQL 的账户,看是否还有依赖于老的 IP 地址段
  • 56. 的账户。比如:eshop 的复制帐号 replicator@172.18.%搬迁到新的机房就肯定需要修改掉。b)heartbeat 的修改;cobar 的修改。机器搬迁到新的机房,需要修改 heartbeat和 cobar 的配置。这样才能让应用访问新的 VIP 地址。c)脚本的修改。目前 MySQL 的管理有许多脚本,这些脚本有些是对 IP 有依赖关系的,需要及时修改掉。当然,最好修改掉这样的脚本,去掉对 IP 的依赖。d)脚本配置文件的修改。有些脚本通过读取配置文件来依赖 IP,同样需要修改配置文件。如果可能的话,修改脚本,去掉对 IP 的依赖。e)带外登录。带外登录地址随着机器 IP 的更换也会变化。搬迁过去以后,需要再次验证一下带外登录的方式是否还正常。该 checklist 可能还不是很完全,需要补充和优化。may you success.