Jiessie's' Blog

没伞的孩子必须努力奔跑


  • 首页

  • 标签

  • 分类

  • 归档

MySQL 慢查询日志介绍

发表于 2018-10-18 | 分类于 MySQL
字数统计: | 阅读时长 ≈

慢查询日志简介

慢查询日志是MySQL提供的一种日志记录形式,由SQL语句组成。用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。慢查询日志可用于查找执行时间较长的查询,因此可作为优化的候选对象。long_query_time的默认值为10,指运行10s以上的语句。

1
2
注意:
MySQL数据库默认不启用慢查询,需要手动来设置这个参数

慢查询相关参数

这里列出5.7版本中与慢查询相关的参数和描述,参考官网

系统变量
及命令行格式
作用域及动态设置
取值及默认值
描述
slow_query_log
–slow-query-log[={OFF/ON}]
全局
YES
OFF/ON
OFF
是否启用慢查询日志
long_query_time
–long-query-time=#
全局/会话
Yes
[0-xxx]
10
慢查询时间阈值
min_examined_row_limit
–min-examined-row-limit=#
全局/会话
Yes
[0-xxx]
0
扫描行数少于此值不会记录慢查询日志
log_slow_admin_statements
–log-slow-admin-statements[={OFF/ON}]
全局
Yes
OFF/ON
OFF
是否记录管理语句,包括 ALTER TABLE, ANALYZE TABLE, CHECK TABLE, CREATE INDEX, DROP INDEX, OPTIMIZE TABLE, and REPAIR TABLE.
log_queries_not_using_indexes
–log-queries-not-using-indexes[={OFF/ON}]
全局
Yes
OFF/ON
0
未使用索引的语句是否记录到慢查询日志
log_throttle_queries_not_using_indexes
–log-throttle-queries-not-using-indexes=#
全局
Yes
[0-xxx]
0
当log_queries_not_using_indexes开启,此参数用来限制每分钟记录到慢查询日志的数量
log_output
–log-output=name
全局
Yes
TABLE/FILE/NONE
FILE
记录general_log和slow.log的日志存储方式
slow_query_log_file
–slow-query-log-file=file_name
全局
Yes
string
hostname-slow.log
慢查询日志文件的名称
-
–log-short-format[={OFF/ON}]
全局
-
OFF/ON
OFF
慢查询日志记录更少的信息
log_slow_slave_statements
–log-slow-slave-statements[={OFF/ON}]
全局
YES
OFF/ON
OFF
作为从库时生效,从库复制中如何有慢sql也将被记录,当binlog格式为的ROW时不生效。并且设置此值不会立即生效,变量的状态应用于所有后续的START SLAVE语句。

慢查询日志内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Time: 2020-05-21T11:48:53.370480+08:00
# User@Host: root[root] @ localhost [] Id: 8
# Query_time: 2.763478 Lock_time: 0.000204 Rows_sent: 0 Rows_examined: 1100003
SET timestamp=1590032930;
insert into employee1 (employeeid,employeename,createtime,jiessie1) select employeeid,employeename,createtime,jiessie1 from employee1;

# Time: 日志产生的时间
# User@Host: 客户端连接信息
# Id: 服务器端线程
# Query_time:查询时长
# Lock_time:锁持有时长
# Rows_sent:返回行数
# Rows_examined:扫描行数

如果mysqld启动时指定了--log-short-format参数,则不会输出第一、第二行。

慢查询参数控制顺序

image

管理语句

  • 开启慢查询:slow_query_log=ON
  • 开启管理语句记录:log_slow_admin_statements = ON
  • 关闭未使用索引的记录:log_queries_not_using_indexes=OFF
  • 关闭从库同步过来是否记录:log_slow_slave_statements=OFF
  • 设置min_examined_row_limit:10000
  • 设置慢查询阈值:0.5s

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    [root@localhost][test][02:21:36]> set global log_slow_admin_statements = ON;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:21:57]> set global log_queries_not_using_indexes = OFF;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:22:03]> set global log_slow_slave_statements = OFF;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:22:08]> set global min_examined_row_limit = 10000;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:22:14]> set global long_query_time = 0.5;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:22:21]> set global slow_query_log_file = 'slow0001.log';
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:22:32]> select * from performance_schema.global_variables where variable_name in ('slow_query_log','long_query_time','min_examined_row_limit','log_slow_admin_statements','log_queries_not_using_indexes','log_throttle_queries_not_using_indexes','log_output','slow_query_log_file','log_slow_slave_statements');
    +----------------------------------------+----------------+
    | VARIABLE_NAME | VARIABLE_VALUE |
    +----------------------------------------+----------------+
    | log_output | FILE |
    | log_queries_not_using_indexes | OFF |
    | log_slow_admin_statements | ON |
    | log_slow_slave_statements | OFF |
    | log_throttle_queries_not_using_indexes | 10 |
    | long_query_time | 0.500000 |
    | min_examined_row_limit | 10000 |
    | slow_query_log | ON |
    | slow_query_log_file | slow0001.log |
    +----------------------------------------+----------------+
    9 rows in set (0.00 sec)

    # 退出下session,重新建立

    [root@localhost][test][02:22:51]> alter table employee1 engine = innodb;
    Query OK, 0 rows affected (1.00 sec)
    Records: 0 Duplicates: 0 Warnings: 0

    # 查看slow.log
    Tcp port: 3306 Unix socket: /opt/mysql3306/data/mysql.sock
    Time Id Command Argument

    # 此时慢查询日志没有记录alter table employee1 engine = innodb;
  • 在以上参数基础上,设置min_examined_row_limit为0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [root@localhost][test][02:22:58]> set session min_examined_row_limit = 0;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][02:24:32]> alter table employee1 engine = innodb;
    Query OK, 0 rows affected (1.09 sec)
    Records: 0 Duplicates: 0 Warnings: 0

    [root@localhost][test][02:25:22]> alter table employee1 modify employeeid int(10) unsigned NOT NULL COMMENT '0',algorithm=copy;
    Query OK, 9999 rows affected, 1 warning (0.94 sec)
    Records: 9999 Duplicates: 0 Warnings: 1

    # 查看slow.log
    Tcp port: 3306 Unix socket: /opt/mysql3306/data/mysql.sock
    Time Id Command Argument
    # Time: 2020-06-03T14:24:35.704816+08:00
    # User@Host: root[root] @ localhost [] Id: 15
    # Query_time: 1.089764 Lock_time: 0.000209 Rows_sent: 0 Rows_examined: 0
    use test;
    SET timestamp=1591165474;
    alter table employee1 engine = innodb;
    # Time: 2020-06-03T14:25:50.639222+08:00
    # User@Host: root[root] @ localhost [] Id: 21
    # Query_time: 0.939964 Lock_time: 0.000218 Rows_sent: 0 Rows_examined: 0
    SET timestamp=1591165549;
    alter table employee1 modify employeeid int(10) unsigned NOT NULL COMMENT '0',algorithm=copy;

    # 02:22:58 设置的min_examined_row_limit为0
    # 02:24:32 再次执行的alter,记录到了slow.log

总结:

慢查询管理语句的记录受参数log_slow_admin_statements、min_examined_row_limit的影响,并不受long_query_time影响。

alter table … ,algorithm=copy;时执行完成显示Query OK, 9999 rows affected,实际慢查询记录的Rows_sent、Rows_examined全是0

查询语句(利用索引)

  • 开启慢查询:slow_query_log=ON
  • 开启管理语句记录:log_slow_admin_statements = OFF
  • 关闭未使用索引的记录:log_queries_not_using_indexes=OFF
  • 关闭从库同步过来是否记录:log_slow_slave_statements=OFF
  • 设置min_examined_row_limit:10001
  • 设置慢查询阈值:0.0000001s

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    [root@localhost][test][03:14:29]> select * from performance_schema.global_variables where variable_name in ('slow_query_log','long_query_time','min_examined_row_limit','log_slow_admin_statements','log_queries_not_using_indexes','log_throttle_queries_not_using_indexes','log_output','slow_query_log_file','log_slow_slave_statements');
    +----------------------------------------+----------------+
    | VARIABLE_NAME | VARIABLE_VALUE |
    +----------------------------------------+----------------+
    | log_output | FILE |
    | log_queries_not_using_indexes | OFF |
    | log_slow_admin_statements | OFF |
    | log_slow_slave_statements | OFF |
    | log_throttle_queries_not_using_indexes | 10 |
    | long_query_time | 0.500000 |
    | min_examined_row_limit | 10000 |
    | slow_query_log | ON |
    | slow_query_log_file | slow0001.log |
    +----------------------------------------+----------------+
    9 rows in set (0.00 sec)

    [root@localhost][test][03:14:31]> set session long_query_time = 0.0000001;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][03:14:39]> set session min_examined_row_limit = 10001;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][03:14:44]> explain select count(*) from employee1 where update_time > '20200501';
    +----+-------------+-----------+--------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+-----------+--------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
    | 1 | SIMPLE | employee1 | P202005,PMAX | range | PRIMARY | PRIMARY | 5 | NULL | 4999 | 100.00 | Using where; Using index |
    +----+-------------+-----------+--------------+-------+---------------+---------+---------+------+------+----------+--------------------------+
    1 row in set, 1 warning (0.00 sec)

    [root@localhost][test][03:14:48]> select count(*) from employee1 where update_time > '20200501';
    +----------+
    | count(*) |
    +----------+
    | 9999 |
    +----------+
    1 row in set (0.01 sec)

    [root@localhost][test][03:14:53]>

    # 查看slow log,此时并没有记录
    # 由于select count(*) from employee1 where update_time > '20200501';扫描行数为4999,小于min_examined_row_limit设置的10001,并且执行时长是大于0.0000001s
  • 将min_examined_row_limit修改为3000

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [root@localhost][test][03:15:01]> set session min_examined_row_limit = 3000;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][03:15:06]> select count(*) from employee1 where update_time > '20200501';
    +----------+
    | count(*) |
    +----------+
    | 9999 |
    +----------+
    1 row in set (0.00 sec)

    [root@localhost][test][03:15:08]>

    # 查看slow log
    # Time: 2020-06-03T15:15:08.108595+08:00
    # User@Host: root[root] @ localhost [] Id: 17
    # Query_time: 0.002721 Lock_time: 0.000141 Rows_sent: 1 Rows_examined: 9999
    SET timestamp=1591168508;
    select count(*) from employee1 where update_time > '20200501';

    # 此时语句记录到了slow log,select count(*) from employee1 where update_time > '20200501';扫描行数为4999,大于min_examined_row_limit设置的3000,并且执行时长是大于0.0000001s

总结:

查询语句(使用索引)的SQL,扫描行数大于min_examined_row_limit的前提下,执行时长如果大于long_query_time则会记录,小于则不会记录;

扫描行数小于min_examined_row_limit的前提下,执行时长的参数设置就不生效;

查询语句(不走索引)

  • 开启慢查询:slow_query_log=ON
  • 开启管理语句记录:log_slow_admin_statements = OFF
  • 关闭未使用索引的记录:log_queries_not_using_indexes=ON
  • 每分钟记录未索引的次数:log_throttle_queries_not_using_indexes=10
  • 关闭从库同步过来是否记录:log_slow_slave_statements=OFF
  • 设置min_examined_row_limit:10000
  • 设置慢查询阈值:0.5s

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    [root@localhost][test][03:31:11]> select * from performance_schema.global_variables where variable_name in ('slow_query_log','long_query_time','min_examined_row_limit','log_slow_admin_statements','log_queries_not_using_indexes','log_throttle_queries_not_using_indexes','log_output','slow_query_log_file','log_slow_slave_statements');
    +----------------------------------------+----------------+
    | VARIABLE_NAME | VARIABLE_VALUE |
    +----------------------------------------+----------------+
    | log_output | FILE |
    | log_queries_not_using_indexes | ON |
    | log_slow_admin_statements | OFF |
    | log_slow_slave_statements | OFF |
    | log_throttle_queries_not_using_indexes | 10 |
    | long_query_time | 0.500000 |
    | min_examined_row_limit | 10000 |
    | slow_query_log | ON |
    | slow_query_log_file | slow0001.log |
    +----------------------------------------+----------------+
    9 rows in set (0.00 sec)

    [root@localhost][test][03:31:15]> select sleep(1);
    +----------+
    | sleep(1) |
    +----------+
    | 0 |
    +----------+
    1 row in set (1.00 sec)

    [root@localhost][test][03:31:36]> select sleep(0.1);
    +-------------+
    | sleep(0.01) |
    +-------------+
    | 0 |
    +-------------+
    1 row in set (0.01 sec)

    # 查看slow log,没有记录日志
    # 由于select sleep(1);扫描行数为0,小于min_examined_row_limit的10000,不管sql执行时长有没有大于long_query_time都不会记录
  • 将min_examined_row_limit修改为0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    [root@localhost][test][03:31:31]> set session min_examined_row_limit = 0;
    Query OK, 0 rows affected (0.00 sec)

    [root@localhost][test][03:31:45]> select sleep(1);
    +----------+
    | sleep(1) |
    +----------+
    | 0 |
    +----------+
    1 row in set (1.00 sec)

    [root@localhost][test][03:31:55]> select sleep(0.1);
    +------------+
    | sleep(0.1) |
    +------------+
    | 0 |
    +------------+
    1 row in set (0.10 sec)

    [root@localhost][test][03:31:59]> select sleep(0.4);
    +------------+
    | sleep(0.4) |
    +------------+
    | 0 |
    +------------+
    1 row in set (0.40 sec)

    [root@localhost][test][03:32:18]> select sleep(0.6);
    +------------+
    | sleep(0.6) |
    +------------+
    | 0 |
    +------------+
    1 row in set (0.60 sec)

    # 查看slow log
    # Time: 2020-06-03T15:31:55.890178+08:00
    # User@Host: root[root] @ localhost [] Id: 19
    # Query_time: 1.000207 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 1
    SET timestamp=1591169514;
    select sleep(1);
    # Time: 2020-06-03T15:32:20.820796+08:00
    # User@Host: root[root] @ localhost [] Id: 19
    # Query_time: 0.600206 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 1
    SET timestamp=1591169540;
    select sleep(0.6);

    # 此时select sleep(1);select sleep(0.6);记录到了慢查询日志,大于min_examined_row_limit的0,满足了long_query_time会记录,不满足long_query_time不会记录

总结:

查询语句(不走索引)的SQL,扫描行数大于min_examined_row_limit的前提下,执行时长如果大于long_query_time则会记录,小于则不会记录;

扫描行数小于min_examined_row_limit的前提下,执行时长的参数设置就不生效;

其他参数

其他参数暂时未做测试

生产建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+----------------------------------------+----------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+----------------------------------------+----------------+
| log_output | FILE |
| log_queries_not_using_indexes | OFF |
| log_slow_admin_statements | ON |
| log_slow_slave_statements | ON |
| log_throttle_queries_not_using_indexes | 10 |
| long_query_time | 0.500000 |
| min_examined_row_limit | 0 |
| slow_query_log | ON |
| slow_query_log_file | slow.log |
+----------------------------------------+----------------+
9 rows in set (0.00 sec)

将log_queries_not_using_indexes设置为OFF的原因是,生产环境可能会有多套监控,当库中表或者其他对象过多时,监控的统计语句或者检索语句因大多从information_schema元数据库取数据,没有走索引,slow log可以会占比较多,会淹没正常的告警。生产的慢SQL完全依赖min_examined_row_limit和long_query_time及log_slow_admin_statements来控制,一类是DDL,别一类正常的DML。

慢查询日志分析工具

1.官方自带工具: mysqldumpslow

2.percona-toolkit工具包中的pt-query-digest

总结

MySQL日常运维工作中,慢查询日志的优化工作能占到日常工作的30%以上,因此对慢查询产生的时机,相关参数,输出内容要有清楚的认识,才能更好的去运维MySQL,最大化的优化慢查询语句,提高数据库服务的稳定性。

gh-ost 使用教程

发表于 2018-10-11 | 分类于 工具
字数统计: | 阅读时长 ≈

引言

gh-ost 是github开源的在线DDL工具,从发布之后就受到了同行的广泛关注,由于其采用的伪装从库的方式同步增量DML,比pt-online-schema-change采用的触发器更友好,且生产开销较小,是一款很优秀的在线DDL工具。

原理

gh-ost 介绍

gh-ost 作为一个伪装的备库,可以从主库/备库上拉取 binlog,过滤之后重新应用到主库上去,相当于主库上的增量操作通过 binlog 又应用回主库本身,不过是应用在幽灵表上。

image

大致工作过程:

1
2
3
1.gh-ost 首先连接到主库上,根据 alter 语句创建幽灵表
2.然后作为一个备库连接到其中一个真正的备库或者主库上(根据具体的参数来定),一边在主库上拷贝已有的数据到幽灵表,一边从备库上拉取增量数据的 binlog,然后不断的把 binlog 应用回主库
3.等待全部数据同步完成,进行 cut-over 幽灵表和原表切换

general_log

通过打开mysql的general_log,查看gh-ost的具体执行过程:

mysql: 5.7.22

gh-ost:1.0.48

此案例使用了–allow-on-master 参数,DDL在主库上操作

执行语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
./gh-ost \
--max-load=Threads_running=25 \
--critical-load=Threads_running=512 \
--chunk-size=1000 \
--initially-drop-old-table \
--initially-drop-ghost-table \
--initially-drop-socket-file \
--ok-to-drop-table \
--conf="/etc/my.cnf" \
--host="localhost" \
--port=3306 \
--user="root" \
--password="xxx" \
--database="test" \
--table="employee1" \
--verbose \
--alter="engine=innodb" \
--switch-to-rbr \
--allow-on-master \
--allow-master-master \
--cut-over=default \
--default-retries=120 \
--panic-flag-file=/tmp/ghost.panic.flag \
--execute

执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
2018-10-11 15:38:27 INFO starting gh-ost 1.0.48
2018-10-11 15:38:27 INFO Migrating `test`.`employee1`
2018-10-11 15:38:27 INFO connection validated on localhost:3306
2018-10-11 15:38:27 INFO User has ALL privileges
2018-10-11 15:38:27 INFO binary logs validated on localhost:3306
2018-10-11 15:38:27 INFO Restarting replication on localhost:3306 to make sure binlog settings apply to replication thread
2018-10-11 15:38:27 INFO Inspector initiated on VM_24_101_centos:3306, version 5.7.22-log
2018-10-11 15:38:27 INFO Table found. Engine=InnoDB
2018-10-11 15:38:28 INFO Estimated number of rows via EXPLAIN: 10028
2018-10-11 15:38:28 INFO Recursively searching for replication master
2018-10-11 15:38:28 INFO Master found to be VM_24_101_centos:3306
2018-10-11 15:38:28 INFO log_slave_updates validated on localhost:3306
2018-10-11 15:38:28 INFO connection validated on localhost:3306
2018-10-11 15:38:28 INFO Connecting binlog streamer at bin.000070:320607755
[2020/05/29 15:38:28] [info] binlogsyncer.go:133 create BinlogSyncer with config {99999 mysql localhost 3306 root false false <nil> false UTC true 0 0s 0s 0 false}
[2020/05/29 15:38:28] [info] binlogsyncer.go:354 begin to sync binlog from position (bin.000070, 320607755)
[2020/05/29 15:38:28] [info] binlogsyncer.go:203 register slave for master server localhost:3306
2018-10-11 15:38:28 INFO rotate to next log from bin.000070:0 to bin.000070
[2020/05/29 15:38:28] [info] binlogsyncer.go:723 rotate to (bin.000070, 320607755)
2018-10-11 15:38:28 INFO connection validated on localhost:3306
2018-10-11 15:38:28 INFO connection validated on localhost:3306
2018-10-11 15:38:28 INFO will use time_zone='SYSTEM' on applier
2018-10-11 15:38:28 INFO Examining table structure on applier
2018-10-11 15:38:28 INFO Applier initiated on VM_24_101_centos:3306, version 5.7.22-log
2018-10-11 15:38:28 INFO Dropping table `test`.`_employee1_gho`
2018-10-11 15:38:28 INFO Table dropped
2018-10-11 15:38:28 INFO Dropping table `test`.`_employee1_del`
2018-10-11 15:38:28 INFO Table dropped
2018-10-11 15:38:28 INFO Dropping table `test`.`_employee1_ghc`
2018-10-11 15:38:28 INFO Table dropped
2018-10-11 15:38:28 INFO Creating changelog table `test`.`_employee1_ghc`
2018-10-11 15:38:28 INFO Changelog table created
2018-10-11 15:38:28 INFO Creating ghost table `test`.`_employee1_gho`
2018-10-11 15:38:28 INFO Ghost table created
2018-10-11 15:38:28 INFO Altering ghost table `test`.`_employee1_gho`
2018-10-11 15:38:28 INFO Ghost table altered
2018-10-11 15:38:28 INFO Intercepted changelog state GhostTableMigrated
2018-10-11 15:38:28 INFO Waiting for ghost table to be migrated. Current lag is 0s
2018-10-11 15:38:28 INFO Handled changelog state GhostTableMigrated
2018-10-11 15:38:28 INFO Chosen shared unique key is PRIMARY
2018-10-11 15:38:28 INFO Shared columns are id,employeeid,employeename
2018-10-11 15:38:28 INFO Listening on unix socket file: /tmp/gh-ost.test.employee1.sock
2018-10-11 15:38:28 INFO Migration min values: [1]
2018-10-11 15:38:28 INFO Migration max values: [10000]
2018-10-11 15:38:28 INFO Waiting for first throttle metrics to be collected
2018-10-11 15:38:28 INFO First throttle metrics collected
# Migrating `test`.`employee1`; Ghost table is `test`.`_employee1_gho`
# Migrating VM_24_101_centos:3306; inspecting VM_24_101_centos:3306; executing on VM_24_101_centos
# Migration started at Fri May 29 15:38:27 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: Threads_running=25; critical-load: Threads_running=512; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# panic-flag-file: /tmp/ghost.panic.flag
# Serving on unix socket: /tmp/gh-ost.test.employee1.sock
Copy: 0/10028 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: bin.000070:320610958; State: migrating; ETA: N/A
Copy: 0/10028 0.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: bin.000070:320618234; State: migrating; ETA: N/A
2018-10-11 15:38:29 INFO Row copy complete
Copy: 10000/10000 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: bin.000070:320813452; State: migrating; ETA: due
2018-10-11 15:38:29 INFO Grabbing voluntary lock: gh-ost.40312.lock
2018-10-11 15:38:29 INFO Setting LOCK timeout as 6 seconds
2018-10-11 15:38:29 INFO Looking for magic cut-over table
2018-10-11 15:38:29 INFO Creating magic cut-over table `test`.`_employee1_del`
2018-10-11 15:38:29 INFO Magic cut-over table created
2018-10-11 15:38:29 INFO Locking `test`.`employee1`, `test`.`_employee1_del`
2018-10-11 15:38:29 INFO Tables locked
2018-10-11 15:38:29 INFO Session locking original & magic tables is 40312
2018-10-11 15:38:29 INFO Writing changelog state: AllEventsUpToLockProcessed:1590737909807671836
2018-10-11 15:38:29 INFO Intercepted changelog state AllEventsUpToLockProcessed
2018-10-11 15:38:29 INFO Handled changelog state AllEventsUpToLockProcessed
2018-10-11 15:38:29 INFO Waiting for events up to lock
Copy: 10000/10000 100.0%; Applied: 0; Backlog: 1/1000; Time: 2s(total), 1s(copy); streamer: bin.000070:320823747; State: migrating; ETA: due
2018-10-11 15:38:30 INFO Waiting for events up to lock: got AllEventsUpToLockProcessed:1590737909807671836
2018-10-11 15:38:30 INFO Done waiting for events up to lock; duration=974.564957ms
# Migrating `test`.`employee1`; Ghost table is `test`.`_employee1_gho`
# Migrating VM_24_101_centos:3306; inspecting VM_24_101_centos:3306; executing on VM_24_101_centos
# Migration started at Fri May 29 15:38:27 +0800 2020
# chunk-size: 1000; max-lag-millis: 1500ms; dml-batch-size: 10; max-load: Threads_running=25; critical-load: Threads_running=512; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# panic-flag-file: /tmp/ghost.panic.flag
# Serving on unix socket: /tmp/gh-ost.test.employee1.sock
Copy: 10000/10000 100.0%; Applied: 0; Backlog: 0/1000; Time: 2s(total), 1s(copy); streamer: bin.000070:320825878; State: migrating; ETA: due
2018-10-11 15:38:30 INFO Setting RENAME timeout as 3 seconds
2018-10-11 15:38:30 INFO Session renaming tables is 40314
2018-10-11 15:38:30 INFO Issuing and expecting this to block: rename /* gh-ost */ table `test`.`employee1` to `test`.`_employee1_del`, `test`.`_employee1_gho` to `test`.`employee1`
Copy: 10000/10000 100.0%; Applied: 0; Backlog: 0/1000; Time: 3s(total), 1s(copy); streamer: bin.000070:320832073; State: migrating; ETA: due
2018-10-11 15:38:31 INFO Found atomic RENAME to be blocking, as expected. Double checking the lock is still in place (though I don t strictly have to)
2018-10-11 15:38:31 INFO Checking session lock: gh-ost.40312.lock
2018-10-11 15:38:31 INFO Connection holding lock on original table still exists
2018-10-11 15:38:31 INFO Will now proceed to drop magic table and unlock tables
2018-10-11 15:38:31 INFO Dropping magic cut-over table
2018-10-11 15:38:31 INFO Releasing lock from `test`.`employee1`, `test`.`_employee1_del`
2018-10-11 15:38:31 INFO Tables unlocked
2018-10-11 15:38:31 INFO Tables renamed
2018-10-11 15:38:31 INFO Lock & rename duration: 2.021372845s. During this time, queries on `employee1` were blocked
2018-10-11 15:38:31 INFO Looking for magic cut-over table
[2020/05/29 15:38:31] [info] binlogsyncer.go:164 syncer is closing...
[2020/05/29 15:38:31] [error] binlogstreamer.go:77 close sync with err: sync is been closing...
2018-10-11 15:38:31 INFO Closed streamer connection. err=<nil>
[2020/05/29 15:38:31] [info] binlogsyncer.go:179 syncer is closed
2018-10-11 15:38:31 INFO Dropping table `test`.`_employee1_ghc`
2018-10-11 15:38:31 INFO Table dropped
2018-10-11 15:38:31 INFO Dropping table `test`.`_employee1_del`
2018-10-11 15:38:31 INFO Table dropped
2018-10-11 15:38:31 INFO Done migrating `test`.`employee1`
2018-10-11 15:38:31 INFO Removing socket file: /tmp/gh-ost.test.employee1.sock
2018-10-11 15:38:31 INFO Tearing down inspector
2018-10-11 15:38:31 INFO Tearing down applier
2018-10-11 15:38:31 INFO Tearing down streamer
2018-10-11 15:38:31 INFO Tearing down throttler
# Done
[mysql@VM_24_101_centos ~]$

打印的general_log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
2018-10-11T15:38:27.796759+08:00	40303 Connect	root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:27.796967+08:00 40303 Query SELECT @@max_allowed_packet
2018-10-11T15:38:27.797151+08:00 40303 Query SET autocommit=true
2018-10-11T15:38:27.797283+08:00 40303 Query SET NAMES utf8mb4
2018-10-11T15:38:27.797446+08:00 40303 Query select @@global.version
2018-10-11T15:38:27.797751+08:00 40303 Query select @@global.port
2018-10-11T15:38:27.797976+08:00 40303 Query select @@global.hostname, @@global.port
2018-10-11T15:38:27.798176+08:00 40303 Query show /* gh-ost */ grants for current_user()
2018-10-11T15:38:27.798307+08:00 40303 Query select @@global.log_bin, @@global.binlog_format
2018-10-11T15:38:27.798396+08:00 40303 Query select @@global.binlog_row_image
2018-10-11T15:38:27.799003+08:00 40304 Connect root@127.0.0.1 on information_schema using TCP/IP
2018-10-11T15:38:27.799177+08:00 40304 Query SELECT @@max_allowed_packet
2018-10-11T15:38:27.799343+08:00 40304 Query SET autocommit=true
2018-10-11T15:38:27.799460+08:00 40304 Query SET NAMES utf8mb4
2018-10-11T15:38:27.799571+08:00 40304 Query show slave status
2018-10-11T15:38:27.799829+08:00 40304 Quit
2018-10-11T15:38:27.799880+08:00 40303 Query show /* gh-ost */ table status from `test` like 'employee1'
2018-10-11T15:38:27.800450+08:00 40303 Query SELECT
SUM(REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_SCHEMA='test' AND TABLE_NAME='employee1') as num_child_side_fk,
SUM(REFERENCED_TABLE_NAME IS NOT NULL AND REFERENCED_TABLE_SCHEMA='test' AND REFERENCED_TABLE_NAME='employee1') as num_parent_side_fk
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE
REFERENCED_TABLE_NAME IS NOT NULL
AND ((TABLE_SCHEMA='test' AND TABLE_NAME='employee1')
OR (REFERENCED_TABLE_SCHEMA='test' AND REFERENCED_TABLE_NAME='employee1')
)
2018-10-11T15:38:28.328510+08:00 40303 Query SELECT COUNT(*) AS num_triggers
FROM INFORMATION_SCHEMA.TRIGGERS
WHERE
TRIGGER_SCHEMA='test'
AND EVENT_OBJECT_TABLE='employee1'
2018-10-11T15:38:28.334174+08:00 40303 Query explain select /* gh-ost */ * from `test`.`employee1` where 1=1
2018-10-11T15:38:28.359593+08:00 40303 Query SELECT
COLUMNS.TABLE_SCHEMA,
COLUMNS.TABLE_NAME,
COLUMNS.COLUMN_NAME,
UNIQUES.INDEX_NAME,
UNIQUES.COLUMN_NAMES,
UNIQUES.COUNT_COLUMN_IN_INDEX,
COLUMNS.DATA_TYPE,
COLUMNS.CHARACTER_SET_NAME,
LOCATE('auto_increment', EXTRA) > 0 as is_auto_increment,
has_nullable
FROM INFORMATION_SCHEMA.COLUMNS INNER JOIN (
SELECT
TABLE_SCHEMA,
TABLE_NAME,
INDEX_NAME,
COUNT(*) AS COUNT_COLUMN_IN_INDEX,
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC) AS COLUMN_NAMES,
SUBSTRING_INDEX(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC), ',', 1) AS FIRST_COLUMN_NAME,
SUM(NULLABLE='YES') > 0 AS has_nullable
FROM INFORMATION_SCHEMA.STATISTICS
WHERE
NON_UNIQUE=0
AND TABLE_SCHEMA = 'test'
AND TABLE_NAME = 'employee1'
GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
) AS UNIQUES
ON (
COLUMNS.COLUMN_NAME = UNIQUES.FIRST_COLUMN_NAME
)
WHERE
COLUMNS.TABLE_SCHEMA = 'test'
AND COLUMNS.TABLE_NAME = 'employee1'
ORDER BY
COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME,
CASE UNIQUES.INDEX_NAME
WHEN 'PRIMARY' THEN 0
ELSE 1
END,
CASE has_nullable
WHEN 0 THEN 0
ELSE 1
END,
CASE IFNULL(CHARACTER_SET_NAME, '')
WHEN '' THEN 0
ELSE 1
END,
CASE DATA_TYPE
WHEN 'tinyint' THEN 0
WHEN 'smallint' THEN 1
WHEN 'int' THEN 2
WHEN 'bigint' THEN 3
ELSE 100
END,
COUNT_COLUMN_IN_INDEX
2018-10-11T15:38:28.361667+08:00 40303 Query show columns from `test`.`employee1`
2018-10-11T15:38:28.362720+08:00 40305 Connect root@127.0.0.1 on information_schema using TCP/IP
2018-10-11T15:38:28.362951+08:00 40305 Query SELECT @@max_allowed_packet
2018-10-11T15:38:28.363171+08:00 40305 Query SET autocommit=true
2018-10-11T15:38:28.363300+08:00 40305 Query SET NAMES utf8mb4
2018-10-11T15:38:28.363417+08:00 40305 Query show slave status
2018-10-11T15:38:28.363647+08:00 40305 Quit
2018-10-11T15:38:28.363739+08:00 40303 Query select @@global.log_slave_updates
2018-10-11T15:38:28.364010+08:00 40303 Query select @@global.version
2018-10-11T15:38:28.364218+08:00 40303 Query select @@global.port
2018-10-11T15:38:28.364408+08:00 40303 Query show /* gh-ost readCurrentBinlogCoordinates */ master status
2018-10-11T15:38:28.365328+08:00 40306 Connect root@127.0.0.1 on using TCP/IP
2018-10-11T15:38:28.365632+08:00 40306 Query SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'
2018-10-11T15:38:28.366882+08:00 40306 Query SET @master_binlog_checksum='NONE'
2018-10-11T15:38:28.367450+08:00 40306 Binlog Dump Log: 'bin.000070' Pos: 320607755
2018-10-11T15:38:28.367748+08:00 40303 Query select @@global.version
2018-10-11T15:38:28.368343+08:00 40303 Query select @@global.port
2018-10-11T15:38:28.368961+08:00 40307 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:28.369102+08:00 40307 Query SELECT @@max_allowed_packet
2018-10-11T15:38:28.369202+08:00 40307 Query SET autocommit=true
2018-10-11T15:38:28.369283+08:00 40307 Query SET NAMES utf8mb4
2018-10-11T15:38:28.369362+08:00 40307 Query select @@global.version
2018-10-11T15:38:28.369513+08:00 40307 Query select @@global.port
2018-10-11T15:38:28.369636+08:00 40303 Query select @@global.time_zone
2018-10-11T15:38:28.369918+08:00 40303 Query select @@global.hostname, @@global.port
2018-10-11T15:38:28.370060+08:00 40303 Query show columns from `test`.`employee1`
2018-10-11T15:38:28.370612+08:00 40303 Query drop /* gh-ost */ table if exists `test`.`_employee1_gho`
2018-10-11T15:38:28.377122+08:00 40303 Query show /* gh-ost */ table status from `test` like '_employee1_gho'
2018-10-11T15:38:28.377393+08:00 40303 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`
2018-10-11T15:38:28.378699+08:00 40303 Query show /* gh-ost */ table status from `test` like '_employee1_del'
2018-10-11T15:38:28.378938+08:00 40303 Query drop /* gh-ost */ table if exists `test`.`_employee1_ghc`
2018-10-11T15:38:28.385496+08:00 40303 Query create /* gh-ost */ table `test`.`_employee1_ghc` (
id bigint auto_increment,
last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
hint varchar(64) charset ascii not null,
value varchar(4096) charset ascii not null,
primary key(id),
unique key hint_uidx(hint)
) auto_increment=256
2018-10-11T15:38:28.419446+08:00 40303 Query create /* gh-ost */ table `test`.`_employee1_gho` like `test`.`employee1`
2018-10-11T15:38:28.445917+08:00 40303 Query alter /* gh-ost */ table `test`.`_employee1_gho` engine=innodb
2018-10-11T15:38:28.480994+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(2, 0), 'state', 'GhostTableMigrated')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.483727+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'state at 1590737908483609931', 'GhostTableMigrated')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.487362+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.487157014+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.487438+08:00 40308 Connect root@127.0.0.1 on information_schema using TCP/IP
2018-10-11T15:38:28.487582+08:00 40308 Query SELECT @@max_allowed_packet
2018-10-11T15:38:28.487684+08:00 40308 Query SET autocommit=true
2018-10-11T15:38:28.487744+08:00 40308 Query SET NAMES utf8mb4
2018-10-11T15:38:28.487897+08:00 40308 Query show slave status
2018-10-11T15:38:28.488401+08:00 40309 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:28.488525+08:00 40309 Query SELECT @@max_allowed_packet
2018-10-11T15:38:28.488614+08:00 40309 Query SET autocommit=true
2018-10-11T15:38:28.488679+08:00 40309 Query SET NAMES utf8mb4
2018-10-11T15:38:28.488893+08:00 40309 Query SELECT
COLUMNS.TABLE_SCHEMA,
COLUMNS.TABLE_NAME,
COLUMNS.COLUMN_NAME,
UNIQUES.INDEX_NAME,
UNIQUES.COLUMN_NAMES,
UNIQUES.COUNT_COLUMN_IN_INDEX,
COLUMNS.DATA_TYPE,
COLUMNS.CHARACTER_SET_NAME,
LOCATE('auto_increment', EXTRA) > 0 as is_auto_increment,
has_nullable
FROM INFORMATION_SCHEMA.COLUMNS INNER JOIN (
SELECT
TABLE_SCHEMA,
TABLE_NAME,
INDEX_NAME,
COUNT(*) AS COUNT_COLUMN_IN_INDEX,
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC) AS COLUMN_NAMES,
SUBSTRING_INDEX(GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX ASC), ',', 1) AS FIRST_COLUMN_NAME,
SUM(NULLABLE='YES') > 0 AS has_nullable
FROM INFORMATION_SCHEMA.STATISTICS
WHERE
NON_UNIQUE=0
AND TABLE_SCHEMA = 'test'
AND TABLE_NAME = '_employee1_gho'
GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
) AS UNIQUES
ON (
COLUMNS.COLUMN_NAME = UNIQUES.FIRST_COLUMN_NAME
)
WHERE
COLUMNS.TABLE_SCHEMA = 'test'
AND COLUMNS.TABLE_NAME = '_employee1_gho'
ORDER BY
COLUMNS.TABLE_SCHEMA, COLUMNS.TABLE_NAME,
CASE UNIQUES.INDEX_NAME
WHEN 'PRIMARY' THEN 0
ELSE 1
END,
CASE has_nullable
WHEN 0 THEN 0
ELSE 1
END,
CASE IFNULL(CHARACTER_SET_NAME, '')
WHEN '' THEN 0
ELSE 1
END,
CASE DATA_TYPE
WHEN 'tinyint' THEN 0
WHEN 'smallint' THEN 1
WHEN 'int' THEN 2
WHEN 'bigint' THEN 3
ELSE 100
END,
COUNT_COLUMN_IN_INDEX
2018-10-11T15:38:28.490030+08:00 40309 Query show columns from `test`.`_employee1_gho`
2018-10-11T15:38:28.490435+08:00 40309 Query select
*
from
information_schema.columns
where
table_schema='test'
and table_name='employee1'
2018-10-11T15:38:28.491220+08:00 40309 Query select
*
from
information_schema.columns
where
table_schema='test'
and table_name='employee1'
2018-10-11T15:38:28.491648+08:00 40309 Query select
*
from
information_schema.columns
where
table_schema='test'
and table_name='_employee1_gho'
2018-10-11T15:38:28.492166+08:00 40303 Query select /* gh-ost `test`.`employee1` */ `id`
from
`test`.`employee1`
order by
`id` asc
limit 1
2018-10-11T15:38:28.492369+08:00 40309 Query select /* gh-ost `test`.`employee1` */ `id`
from
`test`.`employee1`
order by
`id` desc
limit 1
2018-10-11T15:38:28.492686+08:00 40309 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:28.492707+08:00 40303 Query show global status like 'Threads_running'
2018-10-11T15:38:28.493066+08:00 40309 Query show global status like 'Threads_running'
2018-10-11T15:38:28.493758+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'copy iteration 0 at 1590737908', 'Copy: 0/10028 0.0%; Applied: 0; Backlog: 0/1000; Time: 0s(total), 0s(copy); streamer: bin.000070:320610958; State: migrating; ETA: N/A')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.592131+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.591944857+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.593200+08:00 40303 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:28.692156+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.691988298+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.693217+08:00 40309 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:28.792164+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.791963767+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.793212+08:00 40303 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:28.892118+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.891946977+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.893150+08:00 40309 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:28.992167+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.991978+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.993216+08:00 40303 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.105879+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.105683131+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.105878+08:00 40303 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.192162+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.191915709+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.193158+08:00 40309 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.292151+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.291919747+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.293179+08:00 40303 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.392095+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.391930285+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.393205+08:00 40309 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.492159+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.491902668+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.493237+08:00 40303 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.494187+08:00 40310 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:29.494198+08:00 40311 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:29.494760+08:00 40310 Query SELECT @@max_allowed_packet
2018-10-11T15:38:29.494868+08:00 40311 Query SELECT @@max_allowed_packet
2018-10-11T15:38:29.495139+08:00 40311 Query SET autocommit=true
2018-10-11T15:38:29.495258+08:00 40311 Query SET NAMES utf8mb4
2018-10-11T15:38:29.495265+08:00 40310 Query SET autocommit=true
2018-10-11T15:38:29.495466+08:00 40310 Query SET NAMES utf8mb4
2018-10-11T15:38:29.495702+08:00 40310 Query show global status like 'Threads_running'
2018-10-11T15:38:29.496312+08:00 40311 Query select /* gh-ost `test`.`employee1` iteration:0 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.496521+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'copy iteration 0 at 1590737909', 'Copy: 0/10028 0.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: bin.000070:320618234; State: migrating; ETA: N/A')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.496554+08:00 40309 Query show global status like 'Threads_running'
2018-10-11T15:38:29.506595+08:00 40303 Quit
2018-10-11T15:38:29.507086+08:00 40311 Quit
2018-10-11T15:38:29.507153+08:00 40310 Query START TRANSACTION
2018-10-11T15:38:29.507400+08:00 40310 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.507853+08:00 40310 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'1000') or ((`id` = _binary'1000')))) lock in share mode
)
2018-10-11T15:38:29.523454+08:00 40310 Query COMMIT
2018-10-11T15:38:29.533501+08:00 40309 Query select /* gh-ost `test`.`employee1` iteration:1 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'1000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.534075+08:00 40310 Query START TRANSACTION
2018-10-11T15:38:29.534227+08:00 40310 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.534369+08:00 40310 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'1000')) and ((`id` < _binary'2000') or ((`id` = _binary'2000')))) lock in share mode
)
2018-10-11T15:38:29.543074+08:00 40310 Query COMMIT
2018-10-11T15:38:29.586554+08:00 40309 Query select /* gh-ost `test`.`employee1` iteration:2 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'2000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.587919+08:00 40310 Query START TRANSACTION
2018-10-11T15:38:29.588183+08:00 40310 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.588485+08:00 40310 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'2000')) and ((`id` < _binary'3000') or ((`id` = _binary'3000')))) lock in share mode
)
2018-10-11T15:38:29.591994+08:00 40309 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.591866842+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.593465+08:00 40312 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:29.593671+08:00 40312 Query SELECT @@max_allowed_packet
2018-10-11T15:38:29.593802+08:00 40312 Query SET NAMES utf8mb4
2018-10-11T15:38:29.593936+08:00 40312 Query SET autocommit=true
2018-10-11T15:38:29.594077+08:00 40312 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.602754+08:00 40310 Query COMMIT
2018-10-11T15:38:29.611800+08:00 40310 Quit
2018-10-11T15:38:29.612020+08:00 40312 Query select /* gh-ost `test`.`employee1` iteration:3 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'3000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.612725+08:00 40309 Query START TRANSACTION
2018-10-11T15:38:29.612883+08:00 40309 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.613169+08:00 40309 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'3000')) and ((`id` < _binary'4000') or ((`id` = _binary'4000')))) lock in share mode
)
2018-10-11T15:38:29.629626+08:00 40309 Query COMMIT
2018-10-11T15:38:29.672734+08:00 40312 Query select /* gh-ost `test`.`employee1` iteration:4 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'4000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.674106+08:00 40309 Query START TRANSACTION
2018-10-11T15:38:29.674348+08:00 40309 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.674589+08:00 40309 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'4000')) and ((`id` < _binary'5000') or ((`id` = _binary'5000')))) lock in share mode
)
2018-10-11T15:38:29.683215+08:00 40309 Query COMMIT
2018-10-11T15:38:29.688292+08:00 40312 Query select /* gh-ost `test`.`employee1` iteration:5 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'5000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.688930+08:00 40309 Query START TRANSACTION
2018-10-11T15:38:29.689193+08:00 40309 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.689449+08:00 40309 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'5000')) and ((`id` < _binary'6000') or ((`id` = _binary'6000')))) lock in share mode
)
2018-10-11T15:38:29.692033+08:00 40312 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.691873562+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.693417+08:00 40313 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:29.693606+08:00 40313 Query SELECT @@max_allowed_packet
2018-10-11T15:38:29.693715+08:00 40313 Query SET autocommit=true
2018-10-11T15:38:29.693881+08:00 40313 Query SET NAMES utf8mb4
2018-10-11T15:38:29.694047+08:00 40313 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.706414+08:00 40309 Query COMMIT
2018-10-11T15:38:29.710002+08:00 40309 Quit
2018-10-11T15:38:29.710036+08:00 40313 Query select /* gh-ost `test`.`employee1` iteration:6 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'6000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.710568+08:00 40312 Query START TRANSACTION
2018-10-11T15:38:29.710719+08:00 40312 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.710962+08:00 40312 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'6000')) and ((`id` < _binary'7000') or ((`id` = _binary'7000')))) lock in share mode
)
2018-10-11T15:38:29.720031+08:00 40312 Query COMMIT
2018-10-11T15:38:29.724876+08:00 40313 Query select /* gh-ost `test`.`employee1` iteration:7 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'7000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.725421+08:00 40312 Query START TRANSACTION
2018-10-11T15:38:29.725689+08:00 40312 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.725942+08:00 40312 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'7000')) and ((`id` < _binary'8000') or ((`id` = _binary'8000')))) lock in share mode
)
2018-10-11T15:38:29.748448+08:00 40312 Query COMMIT
2018-10-11T15:38:29.754426+08:00 40313 Query select /* gh-ost `test`.`employee1` iteration:8 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'8000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.755265+08:00 40312 Query START TRANSACTION
2018-10-11T15:38:29.755448+08:00 40312 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.755605+08:00 40312 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'8000')) and ((`id` < _binary'9000') or ((`id` = _binary'9000')))) lock in share mode
)
2018-10-11T15:38:29.766785+08:00 40312 Query COMMIT
2018-10-11T15:38:29.770199+08:00 40313 Query select /* gh-ost `test`.`employee1` iteration:9 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'9000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.770741+08:00 40312 Query START TRANSACTION
2018-10-11T15:38:29.770860+08:00 40312 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.771014+08:00 40312 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'9000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))) lock in share mode
)
2018-10-11T15:38:29.778317+08:00 40312 Query COMMIT
2018-10-11T15:38:29.781554+08:00 40313 Query select /* gh-ost `test`.`employee1` iteration:10 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'10000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
2018-10-11T15:38:29.781810+08:00 40312 Query select /* gh-ost `test`.`employee1` iteration:10 */ `id`
from (
select
`id`
from
`test`.`employee1`
where ((`id` > _binary'10000')) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1000
) select_osc_chunk
order by
`id` desc
limit 1
2018-10-11T15:38:29.782135+08:00 40313 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'copy iteration 10 at 1590737909', 'Copy: 10000/10000 100.0%; Applied: 0; Backlog: 0/1000; Time: 1s(total), 1s(copy); streamer: bin.000070:320813452; State: migrating; ETA: due')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.784806+08:00 40312 Query START TRANSACTION
2018-10-11T15:38:29.785567+08:00 40312 Query select connection_id()
2018-10-11T15:38:29.785720+08:00 40312 Query select get_lock('gh-ost.40312.lock', 0)
2018-10-11T15:38:29.785846+08:00 40312 Query set session lock_wait_timeout:=6
2018-10-11T15:38:29.791062+08:00 40313 Query show /* gh-ost */ table status from `test` like '_employee1_del'
2018-10-11T15:38:29.791374+08:00 40313 Query create /* gh-ost */ table `test`.`_employee1_del` (
id int auto_increment primary key
) engine=InnoDB comment='ghost-cut-over-sentry'
2018-10-11T15:38:29.792202+08:00 40314 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:29.793355+08:00 40314 Query SELECT @@max_allowed_packet
2018-10-11T15:38:29.793370+08:00 40315 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:29.793452+08:00 40314 Query SET autocommit=true
2018-10-11T15:38:29.793460+08:00 40315 Query SELECT @@max_allowed_packet
2018-10-11T15:38:29.793515+08:00 40314 Query SET NAMES utf8mb4
2018-10-11T15:38:29.793610+08:00 40315 Query SET autocommit=true
2018-10-11T15:38:29.793646+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.791855122+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.793942+08:00 40315 Query SET NAMES utf8mb4
2018-10-11T15:38:29.794103+08:00 40315 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.807138+08:00 40313 Quit
2018-10-11T15:38:29.807226+08:00 40312 Query lock /* gh-ost */ tables `test`.`employee1` write, `test`.`_employee1_del` write
2018-10-11T15:38:29.807813+08:00 40315 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(2, 0), 'state', 'AllEventsUpToLockProcessed:1590737909807671836')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.810430+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'state at 1590737909810266435', 'AllEventsUpToLockProcessed:1590737909807671836')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.892168+08:00 40315 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.891967268+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.893179+08:00 40314 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:29.992165+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:29.991965407+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:29.993199+08:00 40315 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.092194+08:00 40315 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.091928438+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.093227+08:00 40314 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.192141+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.191949581+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.193143+08:00 40315 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.292137+08:00 40315 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.291941416+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.293149+08:00 40314 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.392240+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.392035339+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.393275+08:00 40315 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.492165+08:00 40315 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.491972791+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.493176+08:00 40314 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.493939+08:00 40316 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:30.494139+08:00 40316 Query SELECT @@max_allowed_packet
2018-10-11T15:38:30.494249+08:00 40316 Query SET autocommit=true
2018-10-11T15:38:30.494318+08:00 40316 Query SET NAMES utf8mb4
2018-10-11T15:38:30.494385+08:00 40316 Query show global status like 'Threads_running'
2018-10-11T15:38:30.494988+08:00 40314 Query show global status like 'Threads_running'
2018-10-11T15:38:30.495802+08:00 40315 Quit
2018-10-11T15:38:30.496482+08:00 40316 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'copy iteration 10 at 1590737910', 'Copy: 10000/10000 100.0%; Applied: 0; Backlog: 1/1000; Time: 2s(total), 1s(copy); streamer: bin.000070:320823747; State: migrating; ETA: due')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.592238+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.591999354+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.593255+08:00 40316 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.692187+08:00 40316 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.691992755+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.693206+08:00 40314 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.782521+08:00 40314 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'copy iteration 10 at 1590737910', 'Copy: 10000/10000 100.0%; Applied: 0; Backlog: 0/1000; Time: 2s(total), 1s(copy); streamer: bin.000070:320825878; State: migrating; ETA: due')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.792059+08:00 40316 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.791873789+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.792295+08:00 40314 Query START TRANSACTION
2018-10-11T15:38:30.792439+08:00 40314 Query select connection_id()
2018-10-11T15:38:30.792794+08:00 40314 Query set session lock_wait_timeout:=3
2018-10-11T15:38:30.793033+08:00 40317 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:30.793157+08:00 40317 Query SELECT @@max_allowed_packet
2018-10-11T15:38:30.793316+08:00 40317 Query SET autocommit=true
2018-10-11T15:38:30.793423+08:00 40317 Query SET NAMES utf8mb4
2018-10-11T15:38:30.793585+08:00 40317 Query select id
from information_schema.processlist
where
id != connection_id()
and 40314 in (0, id)
and state like concat('%', 'metadata lock', '%')
and info like concat('%', 'rename', '%')
2018-10-11T15:38:30.793827+08:00 40314 Query rename /* gh-ost */ table `test`.`employee1` to `test`.`_employee1_del`, `test`.`_employee1_gho` to `test`.`employee1`
2018-10-11T15:38:30.794321+08:00 40318 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:30.794490+08:00 40318 Query SELECT @@max_allowed_packet
2018-10-11T15:38:30.794641+08:00 40318 Query SET autocommit=true
2018-10-11T15:38:30.795166+08:00 40318 Query SET NAMES utf8mb4
2018-10-11T15:38:30.795347+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.796006+08:00 40316 Quit
2018-10-11T15:38:30.892133+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.891962913+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.893297+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:30.992155+08:00 40318 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:30.991958953+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:30.993278+08:00 40317 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.092115+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.091945166+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.093206+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.192121+08:00 40318 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.191943104+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.193195+08:00 40317 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.292113+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.291920755+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.293131+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.392166+08:00 40318 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.391927846+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.393211+08:00 40317 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.492258+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.491963265+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.493225+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.493639+08:00 40318 Query show global status like 'Threads_running'
2018-10-11T15:38:31.494432+08:00 40318 Query show global status like 'Threads_running'
2018-10-11T15:38:31.496602+08:00 40318 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'copy iteration 10 at 1590737911', 'Copy: 10000/10000 100.0%; Applied: 0; Backlog: 0/1000; Time: 3s(total), 1s(copy); streamer: bin.000070:320832073; State: migrating; ETA: due')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.592213+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.592016295+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.593217+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.692115+08:00 40318 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.691941596+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.693157+08:00 40317 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.792078+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.791911518+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.793129+08:00 40318 Query select hint, value from `test`.`_employee1_ghc` where hint = 'heartbeat' and id <= 255
2018-10-11T15:38:31.794412+08:00 40318 Query select id
from information_schema.processlist
where
id != connection_id()
and 40314 in (0, id)
and state like concat('%', 'metadata lock', '%')
and info like concat('%', 'rename', '%')
2018-10-11T15:38:31.794952+08:00 40318 Query select is_used_lock('gh-ost.40312.lock')
2018-10-11T15:38:31.795148+08:00 40312 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`
2018-10-11T15:38:31.804223+08:00 40312 Query unlock tables
2018-10-11T15:38:31.804366+08:00 40312 Query ROLLBACK
2018-10-11T15:38:31.804450+08:00 40312 Quit
2018-10-11T15:38:31.828578+08:00 40314 Query ROLLBACK
2018-10-11T15:38:31.828661+08:00 40318 Query show /* gh-ost */ table status from `test` like '_employee1_del'
2018-10-11T15:38:31.829299+08:00 40318 Quit
2018-10-11T15:38:31.892061+08:00 40317 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:31.891916935+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:31.895474+08:00 40314 Query drop /* gh-ost */ table if exists `test`.`_employee1_ghc`
2018-10-11T15:38:31.903826+08:00 40317 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`
2018-10-11T15:38:31.927905+08:00 40314 Quit
2018-10-11T15:38:31.927992+08:00 40307 Quit
2018-10-11T15:38:31.928011+08:00 40308 Quit
2018-10-11T15:38:31.928082+08:00 40317 Quit

执行过程解析

检查

检查用户、权限、binlog信息、字符集、复制状态、表状态等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2018-10-11T15:38:27.796759+08:00	40303 Connect	root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:27.796967+08:00 40303 Query SELECT @@max_allowed_packet
2018-10-11T15:38:27.797151+08:00 40303 Query SET autocommit=true
2018-10-11T15:38:27.797283+08:00 40303 Query SET NAMES utf8mb4
2018-10-11T15:38:27.797446+08:00 40303 Query select @@global.version
2018-10-11T15:38:27.797751+08:00 40303 Query select @@global.port
2018-10-11T15:38:27.797976+08:00 40303 Query select @@global.hostname, @@global.port
2018-10-11T15:38:27.798176+08:00 40303 Query show /* gh-ost */ grants for current_user()
2018-10-11T15:38:27.798307+08:00 40303 Query select @@global.log_bin, @@global.binlog_format
2018-10-11T15:38:27.798396+08:00 40303 Query select @@global.binlog_row_image
2018-10-11T15:38:27.799003+08:00 40304 Connect root@127.0.0.1 on information_schema using TCP/IP
2018-10-11T15:38:27.799177+08:00 40304 Query SELECT @@max_allowed_packet
2018-10-11T15:38:27.799343+08:00 40304 Query SET autocommit=true
2018-10-11T15:38:27.799460+08:00 40304 Query SET NAMES utf8mb4
2018-10-11T15:38:27.799571+08:00 40304 Query show slave status

检查外键、触发器、行数预估、索引、字段等信息

1
2
3
4
5
6
2018-10-11T15:38:27.800450+08:00	40303 Query	SELECT
SUM(REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_SCHEMA='test' AND TABLE_NAME='employee1') as num_child_side_fk,......
2018-10-11T15:38:28.328510+08:00 40303 Query SELECT COUNT(*) AS num_triggers......
2018-10-11T15:38:28.334174+08:00 40303 Query explain select /* gh-ost */ * from `test`.`employee1` where 1=1......
2018-10-11T15:38:28.359593+08:00 40303 Query SELECT
COLUMNS.TABLE_SCHEMA,......

伪装slave

模拟 slave,获取当前的位点信息,创建 binlog streamer 监听 binlog

1
2
3
4
5
2018-10-11T15:38:28.364408+08:00	40303 Query	show /* gh-ost readCurrentBinlogCoordinates */ master status
2018-10-11T15:38:28.365328+08:00 40306 Connect root@127.0.0.1 on using TCP/IP
2018-10-11T15:38:28.365632+08:00 40306 Query SHOW GLOBAL VARIABLES LIKE 'BINLOG_CHECKSUM'
2018-10-11T15:38:28.366882+08:00 40306 Query SET @master_binlog_checksum='NONE'
2018-10-11T15:38:28.367450+08:00 40306 Binlog Dump Log: 'bin.000070' Pos: 320607755

创建日志记录表 xx_ghc 和影子表 xx_gho 并且执行 alter 语句将影子表变更为目标表结构

1
2
3
4
5
6
7
8
9
10
2018-10-11T15:38:28.385496+08:00	40303 Query	create /* gh-ost */ table `test`.`_employee1_ghc` (
id bigint auto_increment,
last_update timestamp not null DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
hint varchar(64) charset ascii not null,
value varchar(4096) charset ascii not null,
primary key(id),
unique key hint_uidx(hint)
) auto_increment=256
2018-10-11T15:38:28.419446+08:00 40303 Query create /* gh-ost */ table `test`.`_employee1_gho` like `test`.`employee1`
2018-10-11T15:38:28.445917+08:00 40303 Query alter /* gh-ost */ table `test`.`_employee1_gho` engine=innodb

insert into xx_gho select * from xx 拷贝数据

同时从general_log中可以相关的步骤会记录到xx_ghc表中,其中包括GhostTableMigrated,heartbeat等信息,并且此类信息会贯穿DDL整个周期内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2018-10-11T15:38:28.480994+08:00	40303 Query	insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(2, 0), 'state', 'GhostTableMigrated')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.483727+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(0, 0), 'state at 1590737908483609931', 'GhostTableMigrated')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
2018-10-11T15:38:28.487362+08:00 40303 Query insert /* gh-ost */ into `test`.`_employee1_ghc`
(id, hint, value)
values
(NULLIF(1, 0), 'heartbeat', '2018-10-11T15:38:28.487157014+08:00')
on duplicate key update
last_update=NOW(),
value=VALUES(value)
......

紧接着获取获取当前的最大主键和最小主键,然后根据命令行传参 chunk 获取数据 ,开启事务,insert 到影子表里面,循环此步骤,直到数据copy完成

rowcopy 过程中是对原表加上 lock in share mode,防止数据在 copy 的过程中被修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2018-10-11T15:38:29.496312+08:00	40311 Query	select  /* gh-ost `test`.`employee1` iteration:0 */
`id`
from
`test`.`employee1`
where ((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'10000') or ((`id` = _binary'10000')))
order by
`id` asc
limit 1
offset 999
......
2018-10-11T15:38:29.507153+08:00 40310 Query START TRANSACTION
2018-10-11T15:38:29.507400+08:00 40310 Query SET
SESSION time_zone = 'SYSTEM',
sql_mode = CONCAT(@@session.sql_mode, ',STRICT_ALL_TABLES')
2018-10-11T15:38:29.507853+08:00 40310 Query insert /* gh-ost `test`.`employee1` */ ignore into `test`.`_employee1_gho` (`id`, `employeeid`, `employeename`)
(select `id`, `employeeid`, `employeename` from `test`.`employee1` force index (`PRIMARY`)
where (((`id` > _binary'1') or ((`id` = _binary'1'))) and ((`id` < _binary'1000') or ((`id` = _binary'1000')))) lock in share mode
)
2018-10-11T15:38:29.523454+08:00 40310 Query COMMIT
......

应用增量数据

由于测试环境在做DDL期间,没有增量数据写入,general_log并没有读取binlog相关的信息

  • row copy 转化为insert ignore into 到影子表
  • apply binlog 转化为replace into 应用到影子表

创建xx_del表

防止 cut-over 提前执行,导致数据丢失。最后做影子表和原表的交换及收尾工作

1
2
3
4
5
6
7
8
9
2018-10-11T15:38:29.791374+08:00	40313 Query	create /* gh-ost */ table `test`.`_employee1_del` (
id int auto_increment primary key
) engine=InnoDB comment='ghost-cut-over-sentry'......
2018-10-11T15:38:29.807226+08:00 40312 Query lock /* gh-ost */ tables `test`.`employee1` write, `test`.`_employee1_del` write......
2018-10-11T15:38:30.793827+08:00 40314 Query rename /* gh-ost */ table `test`.`employee1` to `test`.`_employee1_del`, `test`.`_employee1_gho` to `test`.`employee1`......
2018-10-11T15:38:31.795148+08:00 40312 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`
2018-10-11T15:38:31.804223+08:00 40312 Query unlock tables......
2018-10-11T15:38:31.895474+08:00 40314 Query drop /* gh-ost */ table if exists `test`.`_employee1_ghc`
2018-10-11T15:38:31.903826+08:00 40317 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`

执行过程总结

  • ① 检查有没有外键和触发器
  • ② 检查表的主键信息
  • ③ 检查是否主库或从库,是否开启log_slave_updates,以及binlog信息
  • ④ 检查gho和del结尾的临时表是否存在
  • ⑤ 创建ghc结尾的表,存数据迁移的信息,以及binlog信息等
  • ⑥ 初始化stream的连接,添加binlog的监听
  • ⑥ 创建gho结尾的临时表,执行DDL在gho结尾的临时表上
  • ⑦ 开启事务,按照主键id把源表数据写入到gho结尾的表上,再提交,以及binlog apply
  • ⑧ lock源表,rename 表:rename 源表 to 源_del表,gho表 to 源表
  • ⑨ 清理ghc表

gh-ost 操作模式

gh-ost 可以同时连接多个服务器,为了获取二进制的数据流,它会作为一个从库,将数据从一个库复制到另外一个。它有各种不同的操作模式,这取决于你的设置,配置,和要运行迁移环境。

image

连接到从库,在主库做迁移

这是 gh-ost 默认的工作方式。gh-ost 将会检查从库状态,找到集群结构中的主库并连接,接下来进行迁移操作:

  • 行数据在主库上读写
  • 读取从库的二进制日志,将变更应用到主库
  • 在从库收集表格式,字段&索引,行数等信息
  • 在从库上读取内部的变更事件(如心跳事件)
  • 在主库切换表

如果你的主库的日志格式是 SBR,工具也可以正常工作。但从库必须启用二级制日志(log_bin, log_slave_updates) 并且设置 binlog_format=ROW ( gh-ost 是读取从库的二级制文件)。

如果直接在主库上操作,当然也需要二进制日志格式是RBR。

连接到主库

如果你没有从库,或者不想使用从库,你可以直接在主库上操作。gh-ost 将会直接在主库上进行所有操作。你需要持续关注复制延迟问题。

  • 主库的二进制日志必须是 RBR 格式
  • 必须指定 –allow-on-master 参数

在从库迁移/测试

该模式会在从库执行迁移操作。gh-ost 会简单的连接到主库,此后所有的操作都在从库执行,不会对主库进行任何的改动。整个操作过程中,gh-ost 将控制速度保证从库可以及时的进行数据同步

  • –migrate-on-replica 表示 gh-ost 会直接在从库上进行迁移操作。即使在复制运行阶段也可以进行表的切换操作
  • –test-on-replica 表示 迁移操作只是为了测试在切换之前复制会停止,然后会进行切换操作,然后在切换回来,你的原始表最终还是原始表。两个表都会保存下来,复制操作是停止的。你可以对这两个表进行一致性检查等测试操作

时序问题

gh-ost 做 DDL 变更期间对原表和影子表的操作有三种:

对原表的 row copy (我们用 A 操作代替),业务对原表的 DML 操作(B),对影子表的 apply binlog(C)。而且 binlog 是基于 DML 操作产生的,因此对影子表的 apply binlog 一定在 对原表的 DML 之后,共有ABC/BCA/BAC/三种

类别 row copy(A) DML(B) apply binlog(C)
A-B-C,数据先被复制到影子表 insert ignore into b select id>0 and id<3;
将数据从原表同步到影子表
insert into b(id,val) values(2,’b’);
update b set val=’b’ where id = 1;
delete from b where id = 1;
replace into b(id,val) values(2,’b’);
update b set val=’b’ where id = 1;
delete from b where id = 1;
B-C-A,先应用dml的binlog,数据后被复制到影子表 insert into b(id,val) values(2,’a’);
update b set val=’b’ where id = 1;
delete from b where id = 1;
replace into b(id,val) values(2,’a’);
update b set val=’b’ where id = 1;影子表不存在此记录,update空;
delete from b where id = 1;影子表不存在此记录,delete空;
因为id=2的记录binlog比row copy先到达,row copy执行insert ignore忽略插入;
row copy时新记录已经在原表,直接复制到影子表即可;
row copy时原表已无此记录,不会同步到影子表
B-A-C,先插入dml,数据再被复制到影子表,最后应用dml insert into b(id,val) values(2,’a’);
update b set val=’b’ where id =1;
delete from b where id = 1;
insert ignore into b(id,val) values(2,’a’);
insert ignore into b(id,val) values(1,’b’);
因为id=1的记录已经被删除,row copy空行;
因为id=2的记录row copy比binlog先到达,影子表已经有id=2记录,apply binlog会使用replace into;
因为id=2的记录row copy比binlog先到达,binlog apply执行update…
binlog apply空操作

通过上面的几种组合操作的分析,我们可以看到 数据最终是一致的。尤其是当copy 结束之后,只剩下apply binlog,情况更简单。

cut-over原理

general_log

从上面的general_log,截取后面的cut-over:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2018-10-11T15:38:29.791374+08:00	40313 Query	create /* gh-ost */ table `test`.`_employee1_del` (
id int auto_increment primary key
) engine=InnoDB comment='ghost-cut-over-sentry'
2018-10-11T15:38:29.807226+08:00 40312 Query lock /* gh-ost */ tables `test`.`employee1` write, `test`.`_employee1_del` write
2018-10-11T15:38:30.493939+08:00 40316 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:30.494139+08:00 40316 Query SELECT @@max_allowed_packet
2018-10-11T15:38:30.494249+08:00 40316 Query SET autocommit=true
2018-10-11T15:38:30.494318+08:00 40316 Query SET NAMES utf8mb4
2018-10-11T15:38:30.494385+08:00 40316 Query show global status like 'Threads_running'
2018-10-11T15:38:30.494988+08:00 40314 Query show global status like 'Threads_running'
2018-10-11T15:38:30.792295+08:00 40314 Query START TRANSACTION
2018-10-11T15:38:30.792439+08:00 40314 Query select connection_id()
2018-10-11T15:38:30.792794+08:00 40314 Query set session lock_wait_timeout:=3
2018-10-11T15:38:30.793033+08:00 40317 Connect root@127.0.0.1 on test using TCP/IP
2018-10-11T15:38:30.793157+08:00 40317 Query SELECT @@max_allowed_packet
2018-10-11T15:38:30.793316+08:00 40317 Query SET autocommit=true
2018-10-11T15:38:30.793423+08:00 40317 Query SET NAMES utf8mb4
2018-10-11T15:38:30.793585+08:00 40317 Query select id
from information_schema.processlist
where
id != connection_id()
and 40314 in (0, id)
and state like concat('%', 'metadata lock', '%')
and info like concat('%', 'rename', '%')
2018-10-11T15:38:30.793827+08:00 40314 Query rename /* gh-ost */ table `test`.`employee1` to `test`.`_employee1_del`, `test`.`_employee1_gho` to `test`.`employee1`
2018-10-11T15:38:31.493639+08:00 40318 Query show global status like 'Threads_running'
2018-10-11T15:38:31.494432+08:00 40318 Query show global status like 'Threads_running'
2018-10-11T15:38:31.794412+08:00 40318 Query select id
from information_schema.processlist
where
id != connection_id()
and 40314 in (0, id)
and state like concat('%', 'metadata lock', '%')
and info like concat('%', 'rename', '%')
2018-10-11T15:38:31.794952+08:00 40318 Query select is_used_lock('gh-ost.40312.lock')
2018-10-11T15:38:31.795148+08:00 40312 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`
2018-10-11T15:38:31.804223+08:00 40312 Query unlock tables
2018-10-11T15:38:31.804366+08:00 40312 Query ROLLBACK
2018-10-11T15:38:31.804450+08:00 40312 Quit
2018-10-11T15:38:31.828578+08:00 40314 Query ROLLBACK
2018-10-11T15:38:31.828661+08:00 40318 Query show /* gh-ost */ table status from `test` like '_employee1_del'
2018-10-11T15:38:31.829299+08:00 40318 Quit
2018-10-11T15:38:31.895474+08:00 40314 Query drop /* gh-ost */ table if exists `test`.`_employee1_ghc`
2018-10-11T15:38:31.903826+08:00 40317 Query drop /* gh-ost */ table if exists `test`.`_employee1_del`
2018-10-11T15:38:31.927905+08:00 40314 Quit
2018-10-11T15:38:31.927992+08:00 40307 Quit
2018-10-11T15:38:31.928011+08:00 40308 Quit
2018-10-11T15:38:31.928082+08:00 40317 Quit

日志说明

其中,包括session:

  • T1-40313:create table _employee1_del
  • T2-40312:lock tables employee1 write, _employee1_del write
  • T3-40316:show global status like ‘Threads_running’
  • T4-40317:select id from information_schema.processlist where id != connection_id() and 40314 in (0, id) and state like concat(‘%’, ‘metadata lock’, ‘%’) and info like concat(‘%’, ‘rename’, ‘%’)
  • T5-40314:rename table employee1 to _employee1_del, _employee1_gho to employee1
  • T6-40318:select id from information_schema.processlist where id != connection_id() and 40314 in (0, id) and state like concat(‘%’, ‘metadata lock’, ‘%’) and info like concat(‘%’, ‘rename’, ‘%’)
  • T7-40318:select is_used_lock(‘gh-ost.40312.lock’)
  • T8-40312:drop table if exists _employee1_del
  • T9-40312:unlock tables
  • T10-40314:drop table if exists _employee1_ghc
  • T11-40317:drop table if exists _employee1_del

日志解析

  • T1 创建临时表_employee1_del,防止提前rename表,导致数据丢失。(如果创建失败,则gh-ost程序退出,此时创建语句并非create table if exists)
  • T2 原表,影子表加写锁,此时原表的DML会阻塞。(如果加锁失败,则gh-ost程序退出)
  • T3 获取实例状态,确定是否进行下一步。(查询状态,不影响上下文)
  • T4 查看当前是否有进程有rename在运行,查询不到的话,可以进行rename操作。(如果查询到rename,下文确定是否获取T2时刻的锁)
  • T5 进行rename交换表,由于在T2时刻表上有写锁,rename会阻塞。(如果此时T2意外中止,rename会退出,因为临时表_employee1_del已经存在)
  • T6 再次查询是否有进程有rename在运行。(如果gh-ost捕捉不到rename,则T2会继续进行下一步T9,所有请求恢复正常)
  • T7 查询到rename操作,确定T2的上锁生效。(如果T2,T5全部失败,T2会释放锁,T5会被清除)
  • T8 删除临时表_employee1_del。(由于T2对_employee1_del加锁,在未unlock tables前,同一session是可以drop操作的。如果drop失败,rename也会失败,因为表对_employee1_del已存在,会继续T9,所有请求恢复正常)
  • T9 释放unlock tables
  • T10-T11 释放其他临时表

官方参考

作者写了三篇文章解释cut-over操作的思路和切换算法,链接1,链接2,链接3,可参考,这里仅列出官方示例说明:

The solution we offer is now based on two connections only (as opposed to three, in the optimistic approach). “Our” connections will be C10, C20. The “normal” app connections are C1..C9, C11..C19, C21..C29.

  • Connections C1..C9 operate on tbl with normal DML: INSERT, UPDATE, DELETE
  • Connection C10: CREATE TABLE tbl_old (id int primary key) COMMENT=’magic-be-here’
  • Connection C10: LOCK TABLES tbl WRITE, tbl_old WRITE
  • Connections C11..C19, newly incoming, issue queries on tbl but are blocked due to the LOCK
  • Connection C20: RENAME TABLE tbl TO tbl_old, ghost TO tbl. This is blocked due to the LOCK, but gets prioritized on top connections C11..C19 and on top C1..C9 or any other connection that attempts DML on tbl
  • Connections C21..C29, newly incoming, issue queries on tbl but are blocked due to the LOCK and due to the RENAME, waiting in queue
  • Connection C10: checks that C20’s RENAME is applied (looks for the blocked RENAME in processlist)
  • Connection 10: DROP TABLE tbl_old. Nothing happens yet; tbl is still locked. All other connections still blocked.
  • Connection 10: UNLOCK TABLES. BAM! The RENAME is first to execute, ghost table is swapped in place of tbl, then C1..C9, C11..C19, C21..C29 all get to operate on the new and shiny tbl

Some notes

  • We create tbl_old as a blocker for a premature swap
  • It is allowed for a connection to DROP a table it has under a WRITE LOCK
  • A blocked RENAME is always prioritized over a blocked INSERT/UPDATE/DELETE, no matter who came first

What happens on failures?

Much fun. Just works; no rollback required.

  • If C10 errors on the CREATE we do not proceed.
  • If C10 errors on the LOCK statement, we do not proceed. The table is not locked. App continues to operate as normal.
  • If C10 dies just as C20 is about to issue the RENAME:

    1
    2
    3
    The lock is released, the queries C1..C9, C11..C19 immediately operate on tbl.
    C20’s RENAME immediately fails because tbl_old exists.
    The entire operation is failed, but nothing terrible happens; some queries were blocked for some time is all. We will need to retry everything
  • If C10 dies while C20 is blocked on RENAME: Mostly similar to the above. Lock released, then C20 fails the RENAME (because tbl_old exists), then all queries resume normal operation

  • If C20 dies before C10 drops the table, we catch the error and let C10 proceed as planned: DROP, UNLOCK. Nothing terrible happens, some queries were blocked for some time. We will need to retry
  • If C20 dies just after C10 DROPs the table but before the unlock, same as above.
  • If both C10 and C20 die, no problem: LOCK is cleared; RENAME lock is cleared. C1..C9, C11..C19, C21..C29 are free to operate on tbl.

No matter what happens, at the end of operation we look for the ghost table. Is it still there? Then we know the operation failed, “atomically”. Is it not there? Then it has been renamed to tbl, and the operation worked atomically.

A side note on failure is the matter of cleaning up the magic tbl_old. Here this is a matter of taste. Maybe just let it live and avoid recreating it, or you can drop it if you like.

对应用的影响

应用程序连接保证被阻止,直到交换 ghost 表或直到操作失败。在前者中,他们继续在新表上进行操作。在后者中,他们继续在原表上进行操作。

参数

以gh-ost 1.0.48版本为准

参数
描述
-aliyun-rds set ‘true’,允许在阿里云数据库上执行
-allow-master-master 是否允许gh-ost运行在双主复制架构中,一般与-assume-master-host参数一起使用
-allow-nullable-unique-key 允许gh-ost在数据迁移依赖的唯一键可以为NULL,默认为不允许为NULL的唯一键。如果数据迁移(migrate)依赖的唯一键允许NULL值,则可能造成数据不正确,请谨慎使用。
-allow-on-master 允许gh-ost直接运行在主库上,默认gh-ost连接的从库。
-alter string string:DDL语句
-approve-renamed-columns ALTER:如果你修改一个列的名字,gh-ost将会识别到并且需要提供重命名列名的原因,默认情况下gh-ost是不继续执行的,除非提供-approve-renamed-columns ALTER。
-ask-pass MySQL密码
-assume-master-host string:为gh-ost指定一个主库,格式为”ip:port”或者”hostname:port”。在这主主架构里比较有用,或则在gh-ost发现不到主的时候有用。
-assume-rbr 确认gh-ost连接的数据库实例的binlog_format=ROW的情况下,可以指定-assume-rbr,这样可以禁止从库上运行stop slave,start slave,执行gh-ost用户也不需要SUPER权限。
-check-flag 检查是否存在/支持另一个标志。 这允许跨版本脚本编写。 当所有其他提供的标志都存在时,以0退出,否则返回非零。 您必须为需要值的标志提供(虚拟)值。 示例:gh-ost –check-flag –cut-over-lock-timeout-seconds –nice-ratio 0
-chunk-size int:在每次迭代中处理的行数量(允许范围:100-100000),默认值为1000。
-concurrent-rowcount 该参数如果为True(默认值),则进行row-copy之后,估算统计行数(使用explain select count(*)方式),并调整ETA时间,否则,gh-ost首先预估统计行数,然后开始row-copy。
-conf gh-ost的配置文件路径。
-critical-load string:一系列逗号分隔的status-name=values组成,当MySQL中status超过对应的values,gh-ost将会退出。-critical-load Threads_connected=20,Connections=1500,指的是当MySQL中的状态值Threads_connected>20,Connections>1500的时候,gh-ost将会由于该数据库严重负载而停止并退出。
-critical-load-hibernate-seconds int:负载达到critical-load时,gh-ost在指定的时间内进入休眠状态。 它不会读/写任何来自任何服务器的任何内容。
-critical-load-interval-millis int:当值为0时,当达到-critical-load,gh-ost立即退出。当值不为0时,当达到-critical-load,gh-ost会在-critical-load-interval-millis秒数后,再次进行检查,再次检查依旧达到-critical-load,gh-ost将会退出。
-cut-over string:选择cut-over类型:atomic/two-step,atomic(默认)类型的cut-over是github的算法,two-step采用的是facebook-OSC的算法。
-cut-over-exponential-backoff 两次失败的切换尝试之间的等待时间间隔成指数增长。 等待间隔服从”exponential-backoff-max-interval”可配置的最大值。
-cut-over-lock-timeout-seconds int:gh-ost在cut-over阶段最大的锁等待时间,当锁超时时,gh-ost的cut-over将重试。(默认值:3)
-database string:数据库名称。
-debug debug模式。
-default-retries int:各种操作在panick前重试次数。(默认为60)
-discard-foreign-keys 该参数针对一个有外键的表,在gh-ost创建ghost表时,并不会为ghost表创建外键。该参数很适合用于删除外键,除此之外,请谨慎使用。
-dml-batch-size int:在单个事务中应用DML事件的批量大小(范围1-100)(默认值为10)
-exact-rowcount 准确统计表行数(使用select count(*)的方式),得到更准确的预估时间。
-execute 实际执行alter&migrate表,默认为noop,不执行,仅仅做测试并退出,如果想要ALTER TABLE语句真正落实到数据库中去,需要明确指定-execute
-exponential-backoff-max-interval 在执行具有指数补偿的各种操作时,两次尝试之间等待的最大秒数。 (默认为64)
-force-named-cut-over 如果为true,则’unpostpone cut-over’交互式命令必须命名迁移的表
-force-table-names string:在临时表上使用的表名前缀
-gcp 在第一代Google Cloud Platform(GCP)上执行时,请将其设置为”true”。
-heartbeat-interval-millis gh-ost心跳频率值,默认为500
-help 帮助
-hooks-hint string:任意消息通过GH_OST_HOOKS_HINT注入到钩子
-hooks-hint-owner 通过GH_OST_HOOKS_HINT_OWNER将所有者的任意名称注入到钩子中
-hooks-hint-token 通过GH_OST_HOOKS_HINT_TOKEN将任意令牌注入到挂钩中
-hooks-path string:hook文件存放目录(默认为empty,即禁用hook)。hook会在这个目录下寻找符合约定命名的hook文件来执行。
-host string :MySQL IP/hostname
-initially-drop-ghost-table gh-ost操作之前,检查并删除已经存在的ghost表。该参数不建议使用,请手动处理原来存在的ghost表。默认不启用该参数,gh-ost直接退出操作。
-initially-drop-old-table gh-ost操作之前,检查并删除已经存在的旧表。该参数不建议使用,请手动处理原来存在的ghost表。默认不启用该参数,gh-ost直接退出操作。
-initially-drop-socket-file gh-ost强制删除已经存在的socket文件。该参数不建议使用,可能会删除一个正在运行的gh-ost程序,导致DDL失败。
-master-password string:MySQL主密码
-master-user string:MysQL主账号
-max-lag-millis 主从复制最大延迟时间,当主从复制延迟时间超过该值后,gh-ost将采取节流(throttle)措施,默认值:1500s。
-max-load string:逗号分隔状态名称=阈值,如:’Threads_running=100,Threads_connected=500’. When status exceeds threshold, app throttles writes
-migrate-on-replica gh-ost的数据迁移(migrate)运行在从库上,而不是主库上。
-nice-ratio float:每次chunk时间段的休眠时间,范围[0.0…100.0]。0:每个chunk时间段不休眠,即一个chunk接着一个chunk执行;1:每row-copy 1毫秒,则另外休眠1毫秒;0.7:每row-copy 10毫秒,则另外休眠7毫秒。
-ok-to-drop-table gh-ost操作结束后,删除旧表,默认状态是不删除旧表,会存在_tablename_del表。
-panic-flag-file string:当这个文件被创建,gh-ost将会立即退出。
-password string:MySQL密码
-port int:MySQL端口
-postpone-cut-over-flag-file string:当这个文件存在的时候,gh-ost的cut-over阶段将会被推迟,数据仍然在复制,直到该文件被删除。
-quiet 静默模式
-replica-server-id uint:gh-ost的server_id
-replication-lag-query string:弃用
-serve-socket-file string:gh-ost的socket文件绝对路径。
-serve-tcp-port int:gh-ost使用端口,默认为关闭端口。
-skip-foreign-key-checks 确定你的表上没有外键时,设置为’true’,并且希望跳过gh-ost验证的时间-skip-renamed-columns ALTER
-skip-renamed-columns ALTER:如果你修改一个列的名字(如change column),gh-ost将会识别到并且需要提供重命名列名的原因,默认情况下gh-ost是不继续执行的。该参数告诉gh-ost跳该列的数据迁移,让gh-ost把重命名列作为无关紧要的列。该操作很危险,你会损失该列的所有值。
-ssl ssl连接MySQL
-ssl-allow-insecure Skips verification of MySQL hosts’ certificate chain and host name. Requires –ssl
-ssl-ca CA certificate in PEM format for TLS connections to MySQL hosts. Requires –ssl
-stack 添加错误堆栈追踪。
-switch-to-rbr 让gh-ost自动将从库的binlog_format转换为ROW格式。
-table string:表名
-test-on-replica 在从库上测试gh-ost,包括在从库上数据迁移(migration),数据迁移完成后stop slave,原表和ghost表立刻交换而后立刻交换回来。继续保持stop slave,使你可以对比两张表。
-test-on-replica-skip-replica-stop 当-test-on-replica执行时,该参数表示该过程中不用stop slave。
-throttle-additional-flag-file string:当该文件被创建后,gh-ost操作立即停止。该参数可以用在多个gh-ost同时操作的时候,创建一个文件,让所有的gh-ost操作停止,或者删除这个文件,让所有的gh-ost操作恢复。
-throttle-control-replicas string:列出所有需要被检查主从复制延迟的从库。
-throttle-flag-file string:当该文件被创建后,gh-ost操作立即停止。该参数适合控制单个gh-ost操作。-throttle-additional-flag-file string适合控制多个gh-ost操作。
-throttle-http when given, gh-ost checks given URL via HEAD request; any response code other than 200 (OK) causes throttling; make sure it has low latency response -throttle-query string
-throttle-query string:节流查询。每秒钟执行一次。当返回值=0时不需要节流,当返回值>0时,需要执行节流操作。该查询会在数据迁移(migrated)服务器上操作,所以请确保该查询是轻量级的。
-timestamp-old-table 在旧表名中使用时间戳。 这会使旧表名称具有唯一且无冲突的交叉迁移
-tungsten 告诉gh-ost你正在运行的是一个tungsten-replication拓扑结构。
-user string:MYSQL用户
-verbose verbose
-version 版本

案例

单实例DDL

相当于主库,需要开启–allow-on-master参数和ROW模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
./gh-ost \
--max-load=Threads_running=25 \
--critical-load=Threads_running=512 \
--chunk-size=1000 \
--initially-drop-old-table \
--initially-drop-ghost-table \
--initially-drop-socket-file \
--ok-to-drop-table \
--conf="/etc/my.cnf" \
--host="master:ip" \
--port=3306 \
--user="root" \
--password="xxx" \
--database="sysbench" \
--table="sbtest1" \
--verbose \
--alter="engine=innodb" \
--switch-to-rbr \
--allow-on-master \
--allow-master-master \
--cut-over=default \
--default-retries=120 \
--panic-flag-file=/tmp/ghost.panic.flag \
--serve-socket-file=/tmp/gh-ost.socket.sock \
--execute

主从上DDL

有2个选择,一是按照单实例DDL直接在主上执行同步到从上,另一个连接到从库,在主库做迁移(只要保证从库的binlog为ROW即可,主库不需要保证),以下去除了参数–allow-on-master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
./gh-ost \
--max-load=Threads_running=25 \
--critical-load=Threads_running=512 \
--chunk-size=1000 \
--initially-drop-old-table \
--initially-drop-ghost-table \
--initially-drop-socket-file \
--ok-to-drop-table \
--conf="/etc/my.cnf" \
--host="slave:ip" \
--port=3306 \
--user="root" \
--password="xxx" \
--database="sysbench" \
--table="sbtest1" \
--verbose \
--alter="engine=innodb" \
--switch-to-rbr \
--allow-master-master \
--cut-over=default \
--default-retries=120 \
--panic-flag-file=/tmp/ghost.panic.flag \
--serve-socket-file=/tmp/gh-ost.socket.sock \
--execute

注意:

  • 在执行DDL中,从库会执行一次stop/start slave,要是确定从的binlog是ROW的话可以添加参数:–assume-rbr。如果从库的binlog不是ROW,可以用参数–switch-to-rbr来转换成ROW,此时需要注意的是执行完毕之后,binlog模式不会被转换成原来的值。–assume-rbr和–switch-to-rbr参数不能一起使用。
  • –host需要连接从库的ip

非分区表改为分区表

1.将原表的id自增改为非自增,使用mysql online ddl,不锁表的方式

1
alter table employee1 modify id int(11) NOT NULL,algorithm=inplace,lock=none;

2.删除分区字段内的空值,省略

3.删除原主键,添加联合主键

1
alter table employee1 drop primary key,ADD PRIMARY KEY (create_time,id),algorithm=inplace,lock=none;

4.gh-ost重建分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
./gh-ost \
--max-load=Threads_running=25 \
--critical-load=Threads_running=512 \
--chunk-size=1000 \
--initially-drop-old-table \
--initially-drop-ghost-table \
--initially-drop-socket-file \
--ok-to-drop-table \
--conf="/etc/my.cnf" \
--host="localhost" \
--port=3306 \
--user="root" \
--password="xxx" \
--database="sysbench" \
--table="employee1" \
--verbose \
--alter=" partition by RANGE COLUMNS (create_time)(PARTITION P202003 VALUES LESS THAN ('2020-04-01'),PARTITION P202004 VALUES LESS THAN ('2020-05-01'),PARTITION P202005 VALUES LESS THAN ('2020-06-01'), PARTITION PMAX VALUES LESS THAN MAXVALUE ) " \
--switch-to-rbr \
--allow-on-master \
--allow-master-master \
--cut-over=default \
--default-retries=120 \
--panic-flag-file=/tmp/ghost.panic.flag \
--serve-socket-file=/tmp/gh-ost.socket.sock \
--execute

终止、暂停、限速

  • ① 表示文件终止运行:–panic-flag-file。
  • 创建文件终止运行,例子中创建/tmp/ghost.panic.flag文件,终止正在运行的gh-ost,临时文件清理需要手动进行。
  • ② 表示文件禁止cut-over进行,即禁止表名切换,数据复制正常进行。–postpone-cut-over-flag-file
  • 创建文件延迟cut-over进行,即推迟切换操作。例子中创建/tmp/ghost.postpone.flag文件,gh-ost 会完成行复制,但并不会切换表,它会持续的将原表的数据更新操作同步到临时表中。(适用于夜里变更时间较长,担心自动cut-over失败,延迟cut-over,早上上班后再做切换)
  • ③ 使用socket监听请求,操作者可以在命令运行后更改相应的参数。–serve-socket-file,–serve-tcp-port(默认关闭)
  • 创建socket文件进行监听,通过接口进行参数调整,当执行操作的过程中发现负载、延迟上升了,不得不终止操作,重新配置参数,如 chunk-size,然后重新执行操作命令,可以通过scoket接口进行动态调整。如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    暂停操作:
    #暂停
    echo throttle | socat - /tmp/gh-ost.socket.sock
    #恢复
    echo no-throttle | socat - /tmp/gh-ost.socket.sock

    修改限速参数:
    echo chunk-size=100 | socat - /tmp/gh-ost.socket.sock
    echo max-lag-millis=200 | socat - /tmp/gh-ost.socket.sock
    echo max-load=Thread_running=3 | socat - /tmp/gh-ost.socket.sock

    以下为可操作的参数:
    [root@VM_24_101_centos ~]# echo help | nc -U /tmp/gh-ost.socket.sock
    available commands:
    status # Print a detailed status message
    sup # Print a short status message
    coordinates # Print the currently inspected coordinates
    chunk-size=<newsize> # Set a new chunk-size
    dml-batch-size=<newsize> # Set a new dml-batch-size
    nice-ratio=<ratio> # Set a new nice-ratio, immediate sleep after each row-copy operation, float (examples: 0 is aggressive, 0.7 adds 70% runtime, 1.0 doubles runtime, 2.0 triples runtime, ...)
    critical-load=<load> # Set a new set of max-load thresholds
    max-lag-millis=<max-lag> # Set a new replication lag threshold
    replication-lag-query=<query> # Set a new query that determines replication lag (no quotes)
    max-load=<load> # Set a new set of max-load thresholds
    throttle-query=<query> # Set a new throttle-query (no quotes)
    throttle-http=<URL> # Set a new throttle URL
    throttle-control-replicas=<replicas> # Set a new comma delimited list of throttle control replicas
    throttle # Force throttling
    no-throttle # End forced throttling (other throttling may still apply)
    unpostpone # Bail out a cut-over postpone; proceed to cut-over
    panic # panic and quit without cleanup
    help # This message
    - use '?' (question mark) as argument to get info rather than set. e.g. "max-load=?" will just print out current max-load.

注意:

1
--postpone-cut-over-flag-file=/tmp/ghost.postpone.flag  #如果指定该参数,此文件执行时就会创建,默认也就延迟做cut-over,直到文件删除,执行cut-over

总结

gh-ost问世之后,由于其创建的设计模式,对生产的影响较小,已经成为了DDL的标准工具,广泛应用于生产。特别是公司内部有自建的运维平台,gh-ost多数会集成进平台,成为标配工具。即使在MySQL 8.0.12后官方的MySQL Online DDL支持秒级加字段,加索引,但是因为其限制,仅支持部分的场景可以做到秒级。并且,多数公司使用的MySQL版本还停留在5.7版本,目前来看,gh-ost是最优秀的MySQL DDL工具。

参考

https://segmentfault.com/a/1190000006158503
https://segmentfault.com/a/1190000020409138
https://segmentfault.com/a/1190000020417715
https://www.cnblogs.com/zping/p/8876148.html
https://www.cnblogs.com/zhoujinyi/p/9187421.html
https://www.cnblogs.com/mysql-dba/p/9901589.html
https://segmentfault.com/a/1190000007729135
https://blog.csdn.net/poxiaonie/article/details/75331916

pt-online-schema-change 使用教程

发表于 2018-09-20 | 分类于 工具
字数统计: | 阅读时长 ≈

介绍

MySQL生产更改表结构的方式有很多,MySQL官方的Online DDL,github开源的gh-ost,此文仅介绍percona的工具包其中的pt-online-schema-change

原理

general_log

在mysql实例中开启general_log参数,观察pt-online-schema-change的执行过程。

执行pt-online-schema-change的语句如下:

1
pt-online-schema-change --user=root  --password=xxx --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "engine=innodb"  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200" --charset=utf8mb4

以下为执行DDL过程中产生的general_log:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
2018-09-20T14:22:32.070723+08:00	   52 Connect	root@localhost on test using Socket
2018-09-20T14:22:32.071066+08:00 52 Query SHOW VARIABLES LIKE 'innodb\_lock_wait_timeout'
2018-09-20T14:22:32.073617+08:00 52 Query SET SESSION innodb_lock_wait_timeout=1
2018-09-20T14:22:32.073760+08:00 52 Query SHOW VARIABLES LIKE 'lock\_wait_timeout'
2018-09-20T14:22:32.074808+08:00 52 Query SET SESSION lock_wait_timeout=60
2018-09-20T14:22:32.074930+08:00 52 Query SHOW VARIABLES LIKE 'wait\_timeout'
2018-09-20T14:22:32.075913+08:00 52 Query SET SESSION wait_timeout=10000
2018-09-20T14:22:32.076034+08:00 52 Query SELECT @@SQL_MODE
2018-09-20T14:22:32.076153+08:00 52 Query SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'*/
2018-09-20T14:22:32.076290+08:00 52 Query SELECT @@server_id /*!50038 , @@hostname*/
2018-09-20T14:22:32.077001+08:00 53 Connect root@localhost on test using Socket
2018-09-20T14:22:32.077212+08:00 53 Query SHOW VARIABLES LIKE 'innodb\_lock_wait_timeout'
2018-09-20T14:22:32.078706+08:00 53 Query SET SESSION innodb_lock_wait_timeout=1
2018-09-20T14:22:32.078829+08:00 53 Query SHOW VARIABLES LIKE 'lock\_wait_timeout'
2018-09-20T14:22:32.079847+08:00 53 Query SET SESSION lock_wait_timeout=60
2018-09-20T14:22:32.079964+08:00 53 Query SHOW VARIABLES LIKE 'wait\_timeout'
2018-09-20T14:22:32.080952+08:00 53 Query SET SESSION wait_timeout=10000
2018-09-20T14:22:32.081068+08:00 53 Query SELECT @@SQL_MODE
2018-09-20T14:22:32.081183+08:00 53 Query SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'*/
2018-09-20T14:22:32.081314+08:00 53 Query SELECT @@server_id /*!50038 , @@hostname*/
2018-09-20T14:22:32.081664+08:00 52 Query SHOW VARIABLES LIKE 'wsrep_on'
2018-09-20T14:22:32.083211+08:00 52 Query SHOW VARIABLES LIKE 'version%'
2018-09-20T14:22:32.084429+08:00 52 Query SHOW ENGINES
2018-09-20T14:22:32.084774+08:00 52 Query SHOW VARIABLES LIKE 'innodb_version'
2018-09-20T14:22:32.086177+08:00 52 Query SHOW VARIABLES LIKE 'innodb_stats_persistent'
2018-09-20T14:22:32.087447+08:00 52 Query SHOW GLOBAL STATUS LIKE 'Threads_running'
2018-09-20T14:22:32.088579+08:00 52 Query SHOW GLOBAL STATUS LIKE 'Threads_running'
2018-09-20T14:22:32.089437+08:00 52 Query SELECT CONCAT(@@hostname, @@port)
2018-09-20T14:22:32.089777+08:00 52 Query SHOW TABLES FROM `test` LIKE 'employee1'
2018-09-20T14:22:32.131978+08:00 52 Query SELECT VERSION()
2018-09-20T14:22:32.132209+08:00 52 Query SHOW TRIGGERS FROM `test` LIKE 'employee1'
2018-09-20T14:22:32.141352+08:00 52 Query /*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */
2018-09-20T14:22:32.141549+08:00 52 Query USE `test`
2018-09-20T14:22:32.141731+08:00 52 Query SHOW CREATE TABLE `test`.`employee1`
2018-09-20T14:22:32.142086+08:00 52 Query /*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */
2018-09-20T14:22:32.142497+08:00 52 Query EXPLAIN SELECT * FROM `test`.`employee1` WHERE 1=1
2018-09-20T14:22:32.142931+08:00 52 Query SELECT table_schema, table_name FROM information_schema.key_column_usage WHERE referenced_table_schema='test' AND referenced_table_name='employee1'
2018-09-20T14:22:32.216829+08:00 52 Query SHOW VARIABLES LIKE 'wsrep_on'
2018-09-20T14:22:32.219654+08:00 52 Query /*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */
2018-09-20T14:22:32.219802+08:00 52 Query USE `test`
2018-09-20T14:22:32.220004+08:00 52 Query SHOW CREATE TABLE `test`.`employee1`
2018-09-20T14:22:32.220231+08:00 52 Query /*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */
2018-09-20T14:22:32.220455+08:00 52 Query CREATE TABLE `test`.`_employee1_new` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
2018-09-20T14:22:32.336287+08:00 52 Query ALTER TABLE `test`.`_employee1_new` engine=innodb
2018-09-20T14:22:32.499232+08:00 52 Query /*!40101 SET @OLD_SQL_MODE := @@SQL_MODE, @@SQL_MODE := '', @OLD_QUOTE := @@SQL_QUOTE_SHOW_CREATE, @@SQL_QUOTE_SHOW_CREATE := 1 */
2018-09-20T14:22:32.499400+08:00 52 Query USE `test`
2018-09-20T14:22:32.499611+08:00 52 Query SHOW CREATE TABLE `test`.`_employee1_new`
2018-09-20T14:22:32.499866+08:00 52 Query /*!40101 SET @@SQL_MODE := @OLD_SQL_MODE, @@SQL_QUOTE_SHOW_CREATE := @OLD_QUOTE */
2018-09-20T14:22:32.500737+08:00 52 Query SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'DELETE' AND ACTION_TIMING = 'AFTER' AND TRIGGER_SCHEMA = 'test' AND EVENT_OBJECT_TABLE = 'employee1'
2018-09-20T14:22:32.502027+08:00 52 Query SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'UPDATE' AND ACTION_TIMING = 'AFTER' AND TRIGGER_SCHEMA = 'test' AND EVENT_OBJECT_TABLE = 'employee1'
2018-09-20T14:22:32.502989+08:00 52 Query SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'INSERT' AND ACTION_TIMING = 'AFTER' AND TRIGGER_SCHEMA = 'test' AND EVENT_OBJECT_TABLE = 'employee1'
2018-09-20T14:22:32.503974+08:00 52 Query SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'DELETE' AND ACTION_TIMING = 'BEFORE' AND TRIGGER_SCHEMA = 'test' AND EVENT_OBJECT_TABLE = 'employee1'
2018-09-20T14:22:32.504875+08:00 52 Query SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'UPDATE' AND ACTION_TIMING = 'BEFORE' AND TRIGGER_SCHEMA = 'test' AND EVENT_OBJECT_TABLE = 'employee1'
2018-09-20T14:22:32.505829+08:00 52 Query SELECT TRIGGER_SCHEMA, TRIGGER_NAME, DEFINER, ACTION_STATEMENT, SQL_MODE, CHARACTER_SET_CLIENT, COLLATION_CONNECTION, EVENT_MANIPULATION, ACTION_TIMING FROM INFORMATION_SCHEMA.TRIGGERS WHERE EVENT_MANIPULATION = 'INSERT' AND ACTION_TIMING = 'BEFORE' AND TRIGGER_SCHEMA = 'test' AND EVENT_OBJECT_TABLE = 'employee1'
2018-09-20T14:22:32.513721+08:00 52 Query CREATE TRIGGER `pt_osc_test_employee1_del` AFTER DELETE ON `test`.`employee1` FOR EACH ROW DELETE IGNORE FROM `test`.`_employee1_new` WHERE `test`.`_employee1_new`.`id` <=> OLD.`id`
2018-09-20T14:22:32.552672+08:00 52 Query CREATE TRIGGER `pt_osc_test_employee1_upd` AFTER UPDATE ON `test`.`employee1` FOR EACH ROW BEGIN DELETE IGNORE FROM `test`.`_employee1_new` WHERE !(OLD.`id` <=> NEW.`id`) AND `test`.`_employee1_new`.`id` <=> OLD.`id`;REPLACE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) VALUES (NEW.`id`, NEW.`employeeid`, NEW.`employeename`);END
2018-09-20T14:22:32.583800+08:00 52 Query CREATE TRIGGER `pt_osc_test_employee1_ins` AFTER INSERT ON `test`.`employee1` FOR EACH ROW REPLACE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) VALUES (NEW.`id`, NEW.`employeeid`, NEW.`employeename`)
2018-09-20T14:22:32.611701+08:00 52 Query EXPLAIN SELECT * FROM `test`.`employee1` WHERE 1=1
2018-09-20T14:22:32.613770+08:00 52 Query SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) ORDER BY `id` LIMIT 1 /*first lower boundary*/
2018-09-20T14:22:32.614250+08:00 52 Query SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX (`PRIMARY`) WHERE `id` IS NOT NULL ORDER BY `id` LIMIT 1 /*key_len*/
2018-09-20T14:22:32.614536+08:00 52 Query EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ * FROM `test`.`employee1` FORCE INDEX (`PRIMARY`) WHERE `id` >= '1' /*key_len*/
2018-09-20T14:22:32.614982+08:00 52 Query EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) ORDER BY `id` LIMIT 999, 2 /*next chunk boundary*/
2018-09-20T14:22:32.615319+08:00 52 Query SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) ORDER BY `id` LIMIT 999, 2 /*next chunk boundary*/
2018-09-20T14:22:32.615860+08:00 52 Query EXPLAIN SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) AND ((`id` <= '1000')) LOCK IN SHARE MODE /*explain pt-online-schema-change 17673 copy nibble*/
2018-09-20T14:22:32.616300+08:00 52 Query INSERT LOW_PRIORITY IGNORE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1')) AND ((`id` <= '1000')) LOCK IN SHARE MODE /*pt-online-schema-change 17673 copy nibble*/
2018-09-20T14:22:32.652804+08:00 52 Query SHOW WARNINGS
2018-09-20T14:22:32.653297+08:00 52 Query SHOW GLOBAL STATUS LIKE 'Threads_running'
2018-09-20T14:22:32.654684+08:00 52 Query EXPLAIN SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) ORDER BY `id` LIMIT 13729, 2 /*next chunk boundary*/
2018-09-20T14:22:32.655061+08:00 52 Query SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) ORDER BY `id` LIMIT 13729, 2 /*next chunk boundary*/
2018-09-20T14:22:32.657464+08:00 52 Query SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) ORDER BY `id` DESC LIMIT 1 /*last upper boundary*/
2018-09-20T14:22:32.657712+08:00 52 Query EXPLAIN SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) AND ((`id` <= '10000')) LOCK IN SHARE MODE /*explain pt-online-schema-change 17673 copy nibble*/
2018-09-20T14:22:32.658105+08:00 52 Query INSERT LOW_PRIORITY IGNORE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= '1001')) AND ((`id` <= '10000')) LOCK IN SHARE MODE /*pt-online-schema-change 17673 copy nibble*/
2018-09-20T14:22:32.890346+08:00 52 Query SHOW WARNINGS
2018-09-20T14:22:32.890850+08:00 52 Query SHOW GLOBAL STATUS LIKE 'Threads_running'
2018-09-20T14:22:32.892398+08:00 52 Query SHOW VARIABLES LIKE 'version%'
2018-09-20T14:22:32.895063+08:00 52 Query SHOW ENGINES
2018-09-20T14:22:32.895463+08:00 52 Query SHOW VARIABLES LIKE 'innodb_version'
2018-09-20T14:22:32.897284+08:00 52 Query ANALYZE TABLE `test`.`_employee1_new` /* pt-online-schema-change */
2018-09-20T14:22:32.935080+08:00 52 Query RENAME TABLE `test`.`employee1` TO `test`.`_employee1_old`, `test`.`_employee1_new` TO `test`.`employee1`
2018-09-20T14:22:33.051130+08:00 52 Query DROP TABLE IF EXISTS `test`.`_employee1_old`
2018-09-20T14:22:33.147460+08:00 52 Query DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_del`
2018-09-20T14:22:33.153099+08:00 52 Query DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_upd`
2018-09-20T14:22:33.160364+08:00 52 Query DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_ins`
2018-09-20T14:22:33.170939+08:00 52 Query SHOW TABLES FROM `test` LIKE '\_employee1\_new'
2018-09-20T14:22:33.227570+08:00 53 Quit
2018-09-20T14:22:33.230437+08:00 52 Quit

执行过程

  1. 创建一个和要执行alter操作的表一样的新的空表,前缀为_开头,后缀以_new结尾。
  2. 在新表执行alter table 语句,因为是空表,执行速度很快。
  3. 在原表中创建3个触发器,分别对应insert,update,delete操作,在5.6版本中不允许存在其他触发器,会报错,5.7版本中无此限制。
  4. 以一定块大小从原表拷贝数据到临时表,拷贝过程中通过原表上的触发器在原表进行的写操作都会更新到新建的临时表,注意这里INSERT和UPDATE采用INSERT LOW_PRIORITY IGNORE INTO操作,DELETE和UPDATE采用REPLACE INTO操作。
  5. 以原子重命名的方式,将原表名table修改为 _table_old, 将_table_new 表明修改为原表名。
  6. 如果有参考该表的外键,根据alter-foreign-keys-method参数的值,检测外键相关的表,做相应设置的处理。
  7. 默认最后将旧原表删除,触发器删除。

时序问题

这里说明一下复制原表数据和触发器的执行顺序是否会产生数据不一致的问题

UPDATE

1.假设pt-online-schema-change在T(10:00:00)执行

2.假设id=2000050这行数据,在T2(10:01:00)时间进行了update,生产的数据通过触发器同步到了临时表,update方式为先DELETE IGNORE,然后REPLCAE INTO,会有2种情况:

1)如果id=2000050这行数据还没从原表复制到临时表,此时DELETE IGNORE不会匹配到数据,REPLCAE INTO会插入新数据;后面执行到分批从原表复制数据到临时表时,包括将id=2000050这行,由于采用的用INSERT LOW_PRIORITY IGNORE INTO,也就不会插入;

2)如果id=2000050这行数据已经从原表复制到临时表,此时DELETE IGNORE会匹配到数据并删除,REPLCAE INTO重新插入新数据;

3.继续往后执行

INSERT

1.假设pt-online-schema-change在T(10:00:00)执行

2.假设新插入数据id=10000001这行数据,在T2(10:30:00)时间进行,由于新插入的数据原表不存在,通过触发器,采用REPLACE INTO,临时表如果存在这条数据,覆盖掉,理论上通过pt-online-schema-change正常执行,临时表也不会存在此行数据

3.继续往后执行

DELETE

1.假设pt-online-schema-change在T(10:00:00)执行

2.假设id=2000050这行数据,在T2(10:01:00)时间进行了delete,生产的数据通过触发器同步到了临时表,delete方式为先DELETE IGNORE

1)如果id=2000050这行数据还没从原表复制到临时表,此时DELETE IGNORE不会匹配到数据;后面执行到分批从原表复制数据到临时表时,包括将id=2000050这行,由于采用的用INSERT LOW_PRIORITY IGNORE INTO,也就不会插入;

2)如果id=2000050这行数据已经从原表复制到临时表,此时DELETE IGNORE会匹配到数据并删除

3.继续往后执行

用法

pt-online-schema-change [OPTIONS] DSN

pt-online-schema-change在不阻塞读或写的情况下更改表的结构。在DSN中指定数据库和表。

sakila.actor增加字段:

1
pt-online-schema-change --alter "ADD COLUMN c1 INT" D=sakila,t=actor

sakila.actor更改存储引擎为InnoDB:

1
pt-online-schema-change --alter "ENGINE=InnoDB" D=sakila,t=actor

风险

Percona Toolkit已经成熟,并得到了验证,并经过了充分测试,但所有数据库工具都可能对系统和数据库服务器造成风险,在使用此工具前注意:

  • 阅读工具的文档
  • 查看工具已知的bug列表
  • 在非生产服务器上测试
  • 备份生产服务器并验证备份有效性

限制

  1. 表中无主键或唯一索引,pt-online-schema-change拒绝执行。
  2. 复制集群中存在过滤复制,pt-online-schema-change拒绝执行,修改参数为–[no]check-replication-filters
  3. 执行过程中检测到从库延迟,暂停数据复制,调节参数为–max-lag
  4. 执行过程中检测到负载过高,pt-online-schema-change暂停或中止执行,调节参数为–max-load和–critical-load
  5. 不能通过删除一列,然后再新增一列的方式来完成对列的重命名操作。
  6. 新增字段,如果这个字段是NOT NULL,必须要指定default值,否则报错,你必须指定默认值。
  7. 如果是DROP FOREIGN KEY constraint_name , 那么必须指定 _ 加上 constraint_name , 而不是 constraint_name。
  8. 默认会设置锁等待超时时间为1s来避免干扰其他事务的进行,调节参数为–lock-wait-timeout。
  9. 默认如果检测到外键冲突后会拒绝改表,调节参数为–alter-foreign-keys-method。
  10. 该工具不能在PXC(Percona XtraDB Cluster)集群中对myisam表进行改表操作。

参数

–dry-run 和 –execute 互斥,前者为打印,后者为执行

选项

参数 描述
–alter 通过这个选项,就不需要alter table关键字了,可以通过逗号来指定多个修改操作。
例如:ADD COLUMN c1 INT
–alter-foreign-keys-method 如何把外键引用到新表?需要特殊处理带有外键约束的表,以保证它们可以应用到新表.当重命名表的时候,外键关系会带到重命名后的表上。

该工具有两种方法,可以自动找到子表,并修改约束关系。
auto: 在rebuild_constraints和drop_swap两种处理方式中选择一个。
rebuild_constraints:使用 ALTER TABLE语句先删除外键约束,然后再添加.如果子表很大的话,会导致长时间的阻塞。
drop_swap: 执行FOREIGN_KEY_CHECKS=0,禁止外键约束,删除原表,再重命名新表。这种方式很快,也不会产生阻塞,但是有风险:
1.在删除原表和重命名新表的短时间内,表是不存在的,程序会返回错误。
2.如果重命名表出现错误,也不能回滚了.因为原表已经被删除。
none: 类似”drop_swap”的处理方式,但是它不删除原表,并且外键关系会随着重命名转到老表上面。
–[no]analyze-before-swap 默认 yes,在新表与旧表完成转换之前对新表执行ANALYZE TABLE操作,默认会在MySQL5.6及之后版本并且开启innodb_stats_persistent的情况下执行。
–ask-pass 命令行提示密码输入,保护密码安全,前提需安装模块perl-TermReadKey。
–channel 指定当主从复制环境是多源复制时需要进行同步哪个主库的数据,适用于多源复制中多个主库对应一个从库的情形。
–charset 指定连接字符集。
–[no]check-alter 默认 yes,解析变更选项的内容,发出表变更警告,主要警告项为:

1.字段重命名
在工具的早期版本中,通过指定CHANGE COLUMN name new_name进行字段重命名会导致数据库的丢失,现在的版本已经通过代码解决了数据一致性问题。但这段代码并不能保证能够确保数据的不丢失。所以当涉及到字段名变更时应通过添加选项’–dry-run’和’–print’查看变更是否可以正确执行。
2.删除主键
如果’–alter’选项中包含DROP PRIMARY KEY删除主键的操作,除非指定选项’–dry-run’,否则工具将退出。变更表的主键是十分危险的,工具变更时建立的触发器,尤其是DELETE触发器,是基于主键的,在做主键变更前先添加选项’–dry-run’和’–print’验证触发器是可用的。
–check-interval 指定出现从库滞后超过max-lag,则该工具将睡眠多长时间,默认1s,再检查。
–[no]check-plan 默认 yes,检查查询执行计划的安全性。
–[no]check-replication-filters 默认 yes,如果工具检测到服务器选项中有任何复制相关的筛选,如指定 binlog_ignore_db 和 replicate_do_db 此类。发现有这样的筛选,工具会报错且退出。因为如果更新的表 Master 上存在,而 Slave 上不存在,会导致复制的失败。使用–no-check-replication-filters 选项来禁用该检查。
–check-slave-lag 指定一个从库的 DSN 连接地址,如果从库超过 –max-lag 参数设置的值,就会暂停操作。
–chunk-index 指定使用哪个索引对表进行chunk分块操作。默认情况下会选择最优的索引,工具会在SQL语句中添加FORCE INDEX子句。
–chunk-index-columns 指定使用选项’–chunk-index’的索引使用最左前缀几个索引字段,只适用于复合索引。
–chunk-size 指定表分块的chunk大小,每个chunk对应的表行数,默认是 1000 行,也可以是数据块大小,当指定大小时允许的后缀单位为k、M、G。这个块的大小要尽量与 –chunk-time 匹配,如果明确指定这个选项,那么每个块就会指定行数的大小。如果块索引不是唯一的,那么块可能会比期望的大
–chunk-size-limit 当需要复制的块远大于设置的 chunk-size 大小,就不复制。默认值是 4.0,一个没有主键或唯一索引的表,块大小就是不确定的。
–chunk-time 在 chunk-time 执行的时间内,动态调整 chunk-size 的大小,以适应服务器性能的变化,该参数设置为 0, 或者指定 chunk-size, 都可以禁止动态调整。
–config 读取以逗号分隔的配置文件列表,如果指定,则必须要在命令行的第一个选项位置
–critical-load 默认为 Threads_running=50。用法基本与 –max-load 类似,如果不指定 MAX_VALUE,那么工具会这只其为当前值的 200%。如果超过指定值,则工具直接退出,而不是暂停。
–database 指定连接的数据库,如有多个则用’,’(逗号)隔开。
–default-engine 默认情况下,新的表与原始表是相同的存储引擎,所以如果原来的表使用 InnoDB 的,那么新表将使用 InnoDB 的。在涉及复制某些情况下,很可能主从的存储引擎不一样。使用该选项会默认使用默认的存储引擎。
–data-dir 使用DATA DIRECTORY特性在不同的分区上创建新表。只能在5.6以上版本中使用。如果与remove-data-dir同时使用,则忽略此参数。
–remove-data-dir 如果原始表是使用DATA DIRECTORY特性创建的,那么删除它并在MySQL默认目录中创建新表,而不创建新的isl文件。
–defaults-file 读取配置文件。
–[no]drop-new-table 默认 yes。如果拷贝旧表数据到新表时失败,则删除新表。如果指定选项’–no-drop-new-table’以及’–no-swap-tables’将保留一份变更后的副本,但不会对旧表进行修改。-no-drop-new-table不能用于修改-foreign-key -method drop_swap。
–[no]drop-old-table 默认 yes。复制数据完成重命名之后,删除原表,如果有错误则会保留原表,指定选项’–no-swap-tables’同样不会删除旧表。
–[no]drop-trigger 默认 yes,删除原表上的触发器。 –no-drop-triggers 会强制开启 –no-drop-old-table 即:不删除触发器就会强制不删除原表。
–dry-run 创建和修改新表,但不会创建触发器、复制数据、和替换原表。并不真正执行,可以看到生成的执行语句,了解其执行步骤与细节。–dry-run 与 –execute 必须指定一个,二者相互排斥。和 –print 配合最佳。
–execute 确定修改表,则指定该参数。真正执行。–dry-run 与 –execute 必须指定一个,二者相互排斥。
–[no]check-unique-key-change 默认值:yes,当工具要进行添加唯一索引的变更时停止运行。因为工具使用语句INSERT IGNORE从旧表进行数据拷贝插入新表,如果插入的值违返唯一性约束,数据插入不会明确提示失败但这样会造成数据丢失。
–force 强制运行,可能打破外键约束。
–help 打印帮助。
–host 连接的host。
–max-flow-ctl 类似-max-lag,适用于PXC集群。检查用于流控制的平均暂停时间集群,如果超过选项中指定的百分比,则使工具暂停。当检测到任何流控制活动时,值0将使工具暂停。默认情况下没有流控制检查。此选项适用于PXC版本5.6或更高版本。
–max-lag 默认 1s。每个 chunk 拷贝完成后,会查看所有复制 Slave 的延迟情况。要是延迟大于该值,则暂停复制数据,直到所有从的滞后小于这个值,使用 Seconds_Behind_Master。如果有任何从滞后超过此选项的值,则该工具将睡眠 –check-interval 指定的时间,再检查。如果从被停止,将会永远等待,直到从开始同步,并且延迟小于该值。如果指定 –check-slave-lag,该工具只检查该服务器的延迟,而不是所有服务器。
–max-load 默认为 Threads_running=25。每个 chunk 拷贝完后,会检查 SHOW GLOBAL STATUS 的内容,检查指标是否超过了tatus 指标 =MAX_VALUE 或者 status 指标:MAX_VALUE。如果不指定 MAX_VALUE,那么工具会这只其为当前值的 120%。
–preserve-triggers 指定保留旧表的触发器。从MySQL5.7.2起开始支持在同一张给定的表上定义具有相同触发事件和触发时间的多个触发器。这意味着如果表原来已有触发器,那么工具所需的触发器也可以创建成功。如果指定了该选项,则工具将旧表上所有的触发器复制到新表上,然后再进行表数据行的拷贝操作。

限制:
1.如果旧表上的触发器引用了将被工具删除的字段,则触发器失效;
2.该选项不能与选项’–no-drop-triggers’、’–no-drop-old-table’和’–no-swap-tables’一起使用,因为该选项需要删除旧表的触发器并在新表上重新创建,因为表不可能有多个同名的触发器。
–new-table-name 复制创建新表的名称,默认 %T_new。
–null-to-not-null 指定可以将允许NULL的字段转换为NOT NULL字段。其中如有包含NULL行的字段值转换为字段默认值,如果没有字段值,则根字段类型来分配默认值。如:字符串类型为’’(空字符串),数值类型为0。
–only-same-schema-fks 仅在与原始表相同模式的表上检查外键。 此选项很危险,因为如果您有FL引用其他架构中的表,则不会检测到它们。
–password 连接的密码。
–pause-file 当此参数指定的文件存在时,执行将暂停。
–pid 创建给定的PID文件。 如果PID文件已经存在并且包含的PID与当前的PID不同,则该工具将无法启动。但是,如果存在PID文件,并且其中包含的PID不再运行,则该工具将使用当前PID覆盖PID文件。 工具退出后,PID文件将自动删除。
–plugin 定义”pt_online_schema_change_plugin”类的Perl模块文件。 使用插件,您可以编写一个Perl模块,该模块可以连接到pt-online-schema-change的许多部分。 这需要对Perl和Percona Toolkit约定的充分了解,而这些约定不在本文档的讨论范围之内。 如有疑问或需要帮助,请联系Percona。
–port 连接的端口。
–print 打印 SQL 语句到标准输出。指定此选项可以让你看到该工具所执行的语句,和 –dry-run 配合最佳。
–progress 复制数据的时候打印进度报告,二部分组成:第一部分是百分比,第二部分是时间。
–quiet -q,不把信息标准输出。
–recurse 指定搜寻从库的层级,默认无限级。
–recursion-method 默认是 show processlist,发现从的方法,也可以是 host,但需要在从上指定 report_host,通过 show slave hosts 来找到,可以指定 none 来不检查 Slave。

METHOD USES
===== ========
processlist SHOW PROCESSLIST
hosts SHOW SLAVE HOSTS
dsn=DSN DSNs from a table
none Do not find slaves
指定 none 则表示不在乎从的延迟。
–skip-check-slave-lag 检查SLAVE的时候,指定该SLAVE跳过。
–slave-user 设置用于连接到从库的用户。
–slave-password 设置用于连接到从库的密码。
–set-vars 默认:
wait_timeout=10000
innodb_lock_wait_timeout=1
lock_wait_timeout=60
运行检查时指定参数值,如有多个用’,’(逗号)分隔。如’–set-vars wait_timeout=500’。
–sleep 默认值:0s,每个chunk导入后与下一次chunk导入开始前sleep一会,sleep时间越长,对于磁盘IO的冲击就越小。
–socket 连接实例的socket。
–statistics 打印出内部事件的数目,可以看到复制数据插入的数目。
–[no]swap-tables 默认 yes。交换原始表和新表,除非你禁止 –[no]drop-old-table。
–tries 尝试几次关键操作。 如果某些操作由于非致命的可恢复错误而失败,则该工具将等待并再次尝试该操作。
这些是重试的操作,其默认尝试次数和两次尝试之间的等待时间(以秒为单位):

OPERATION TRIES WAIT
======= ==== ====
create_triggers 10 1
drop_triggers 10 1
copy_rows 10 0.25
swap_tables 10 1
update_foreign_keys 10 1
analyze_table 10 1

要更改默认值,请指定新值,例如:–tries create_triggers:5:0.5,drop_triggers:5:0.5
–user 连接的用户。
–version 打印版本信息。
–[no]version-check 默认值:yes,检查Percona Toolkit、MySQL和其他程序的最新版本。

DSN选项

这些选项选用于创建DSN,每个选项都给出了option=value,选项区分大小写

参数 描述
A dsn: charset; copy: yes 默认字符集
D dsn: database; copy: yes 默认数据库
F dsn: mysql_read_default_file; copy: yes 仅从指定文件读取参数
h dsn: host; copy: yes 连接的host
p dsn: password; copy: yes 连接的密码
P dsn: port; copy: yes 连接的端口
S dsn: mysql_socket; copy: yes 连接的socket
u dsn: user; copy: yes 连接的用户
t 记录操作的表,通过–log-dsn指定

案例

创建测试表

通过存储过程,在test库下创建employee1表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
DELIMITER $$
DROP PROCEDURE IF EXISTS my_test1$$
CREATE PROCEDURE my_test1(IN loop_times INT)
BEGIN
DECLARE var INT DEFAULT 0;

PREPARE MSQL FROM 'CREATE TABLE IF NOT EXISTS `employee1` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`employeeid` int(10) unsigned NOT NULL COMMENT ''0'',`employeename` varchar(64) NOT NULL DEFAULT '''',PRIMARY KEY (`id`)) ENGINE=InnoDB';
EXECUTE MSQL;

START TRANSACTION;
WHILE var<loop_times DO
SET var=var+1;
INSERT INTO employee1 (employeeid,employeename) VALUES (var,CONCAT('test',var));

END WHILE;
COMMIT;
END$$
DELIMITER ;

查看表信息及创建过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[root@localhost][test][10:20:39]> CREATE PROCEDURE my_test1(IN loop_times INT)
-> BEGIN
-> DECLARE var INT DEFAULT 0;
->
-> PREPARE MSQL FROM 'CREATE TABLE IF NOT EXISTS `employee1` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`employeeid` int(10) unsigned NOT NULL COMMENT ''0'',`employeename` varchar(64) NOT NULL DEFAULT '''',PRIMARY KEY (`id`)) ENGINE=InnoDB';
-> EXECUTE MSQL;
->
-> START TRANSACTION;
-> WHILE var<loop_times DO
-> SET var=var+1;
-> INSERT INTO employee1 (employeeid,employeename) VALUES (var,CONCAT('test',var));
->
-> END WHILE;
-> COMMIT;
-> END$$
Query OK, 0 rows affected (0.02 sec)

[root@localhost][test][10:20:39]> DELIMITER ;
[root@localhost][test][10:20:39]>
[root@localhost][test][10:20:43]> CALL my_test1(10000);
Query OK, 0 rows affected (0.93 sec)

[root@localhost][test][10:20:59]> select * from employee1 limit 10;
+----+------------+--------------+
| id | employeeid | employeename |
+----+------------+--------------+
| 1 | 1 | test1 |
| 2 | 2 | test2 |
| 3 | 3 | test3 |
| 4 | 4 | test4 |
| 5 | 5 | test5 |
| 6 | 6 | test6 |
| 7 | 7 | test7 |
| 8 | 8 | test8 |
| 9 | 9 | test9 |
| 10 | 10 | test10 |
+----+------------+--------------+
10 rows in set (0.00 sec)

[root@localhost][test][10:21:08]> show create table employee1\G
*************************** 1. row ***************************
Table: employee1
Create Table: CREATE TABLE `employee1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

添加字段

添加字段语句:

1
pt-online-schema-change --user=root  --password=111111 --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "ADD COLUMN update_time datetime  DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间';"  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200" --charset=utf8mb4

执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
[root@VM_24_101_centos ~]# pt-online-schema-change --user=root  --password=111111 --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "ADD COLUMN update_time datetime  DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间';"  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200" --charset=utf8mb4
No slaves found. See --recursion-method if host VM_24_101_centos has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
No foreign keys reference `test`.`employee1`; ignoring --alter-foreign-keys-method.
Altering `test`.`employee1`...
Creating new table...
CREATE TABLE `test`.`_employee1_new` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Created new table test._employee1_new OK.
Altering new table...
ALTER TABLE `test`.`_employee1_new` ADD COLUMN update_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间';
Altered `test`.`_employee1_new` OK.
2018-09-21T10:33:08 Creating triggers...
2018-09-21T10:33:08 Created triggers OK.
2018-09-21T10:33:08 Copying approximately 10000 rows...
INSERT LOW_PRIORITY IGNORE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 10925 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/
2018-09-21T10:33:08 Copied rows OK.
2018-09-21T10:33:08 Analyzing new table...
2018-09-21T10:33:08 Swapping tables...
RENAME TABLE `test`.`employee1` TO `test`.`_employee1_old`, `test`.`_employee1_new` TO `test`.`employee1`
2018-09-21T10:33:08 Swapped original and new tables OK.
2018-09-21T10:33:08 Dropping old table...
DROP TABLE IF EXISTS `test`.`_employee1_old`
2018-09-21T10:33:08 Dropped old table `test`.`_employee1_old` OK.
2018-09-21T10:33:08 Dropping triggers...
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_del`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_upd`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_ins`
2018-09-21T10:33:08 Dropped triggers OK.
Successfully altered `test`.`employee1`.
[root@VM_24_101_centos ~]# mysql test -e "show create table employee1\G"
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Table: employee1
Create Table: CREATE TABLE `employee1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
[root@VM_24_101_centos ~]# mysql test -e "select * from employee1 limit 10"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+------------+--------------+---------------------+
| id | employeeid | employeename | update_time |
+----+------------+--------------+---------------------+
| 1 | 1 | test1 | 2018-09-21 10:33:08 |
| 2 | 2 | test2 | 2018-09-21 10:33:08 |
| 3 | 3 | test3 | 2018-09-21 10:33:08 |
| 4 | 4 | test4 | 2018-09-21 10:33:08 |
| 5 | 5 | test5 | 2018-09-21 10:33:08 |
| 6 | 6 | test6 | 2018-09-21 10:33:08 |
| 7 | 7 | test7 | 2018-09-21 10:33:08 |
| 8 | 8 | test8 | 2018-09-21 10:33:08 |
| 9 | 9 | test9 | 2018-09-21 10:33:08 |
| 10 | 10 | test10 | 2018-09-21 10:33:08 |
+----+------------+--------------+---------------------+
[root@VM_24_101_centos ~]#

删除字段

删除字段语句:

1
pt-online-schema-change --user=root  --password=111111 --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "DROP COLUMN update_time;"  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200"

执行过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
[root@VM_24_101_centos ~]# pt-online-schema-change --user=root  --password=111111 --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "DROP COLUMN update_time;"  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200" 
No slaves found. See --recursion-method if host VM_24_101_centos has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
No foreign keys reference `test`.`employee1`; ignoring --alter-foreign-keys-method.
Altering `test`.`employee1`...
Creating new table...
CREATE TABLE `test`.`_employee1_new` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '????',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Created new table test._employee1_new OK.
Altering new table...
ALTER TABLE `test`.`_employee1_new` DROP COLUMN update_time;
Altered `test`.`_employee1_new` OK.
2018-09-21T10:36:47 Creating triggers...
2018-09-21T10:36:47 Created triggers OK.
2018-09-21T10:36:47 Copying approximately 10000 rows...
INSERT LOW_PRIORITY IGNORE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 11401 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/
2018-09-21T10:36:48 Copied rows OK.
2018-09-21T10:36:48 Analyzing new table...
2018-09-21T10:36:48 Swapping tables...
RENAME TABLE `test`.`employee1` TO `test`.`_employee1_old`, `test`.`_employee1_new` TO `test`.`employee1`
2018-09-21T10:36:48 Swapped original and new tables OK.
2018-09-21T10:36:48 Dropping old table...
DROP TABLE IF EXISTS `test`.`_employee1_old`
2018-09-21T10:36:48 Dropped old table `test`.`_employee1_old` OK.
2018-09-21T10:36:48 Dropping triggers...
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_del`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_upd`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_ins`
2018-09-21T10:36:48 Dropped triggers OK.
Successfully altered `test`.`employee1`.
[root@VM_24_101_centos ~]# mysql test -e "show create table employee1\G"
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Table: employee1
Create Table: CREATE TABLE `employee1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
[root@VM_24_101_centos ~]#

此时,需要将–charset=utf8mb4删除,不然会报错:Error creating new table: Wide character in print at /bin/pt-online-schema-change line 10510.

改变字段属性

改变字段属性的语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[root@VM_24_101_centos ~]# pt-online-schema-change --user=root  --password=111111 --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "MODIFY employeename varchar(300) NOT NULL DEFAULT '';"  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200" --charset=utf8mb4 
No slaves found. See --recursion-method if host VM_24_101_centos has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
No foreign keys reference `test`.`employee1`; ignoring --alter-foreign-keys-method.
Altering `test`.`employee1`...
Creating new table...
CREATE TABLE `test`.`_employee1_new` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Created new table test._employee1_new OK.
Altering new table...
ALTER TABLE `test`.`_employee1_new` MODIFY employeename varchar(300) NOT NULL DEFAULT '';
Altered `test`.`_employee1_new` OK.
2018-09-21T10:45:34 Creating triggers...
2018-09-21T10:45:34 Created triggers OK.
2018-09-21T10:45:34 Copying approximately 10000 rows...
INSERT LOW_PRIORITY IGNORE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`) SELECT `id`, `employeeid`, `employeename` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) AND ((`id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 12515 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`id` >= ?)) ORDER BY `id` LIMIT ?, 2 /*next chunk boundary*/
2018-09-21T10:45:34 Copied rows OK.
2018-09-21T10:45:34 Analyzing new table...
2018-09-21T10:45:34 Swapping tables...
RENAME TABLE `test`.`employee1` TO `test`.`_employee1_old`, `test`.`_employee1_new` TO `test`.`employee1`
2018-09-21T10:45:34 Swapped original and new tables OK.
2018-09-21T10:45:34 Dropping old table...
DROP TABLE IF EXISTS `test`.`_employee1_old`
2018-09-21T10:45:34 Dropped old table `test`.`_employee1_old` OK.
2018-09-21T10:45:34 Dropping triggers...
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_del`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_upd`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_ins`
2018-09-21T10:45:34 Dropped triggers OK.
Successfully altered `test`.`employee1`.
[root@VM_24_101_centos ~]# mysql test -e "show create table employee1\G"
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Table: employee1
Create Table: CREATE TABLE `employee1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(300) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

非分区表改为分区表

1.将原表的id自增改为非自增,使用mysql online ddl,不锁表的方式

1
mysql test -e "alter table employee1 modify id int(10) unsigned NOT NULL,algorithm=inplace,lock=none;"

2.删除分区字段内的空值,省略

3.删除原主键,添加联合主键

1
mysql test -e "alter table employee1 drop primary key,ADD PRIMARY KEY (update_time,id),algorithm=inplace,lock=none;"

4.使用pt-online-schema-change建分区

1
pt-online-schema-change --user=root  --password=111111 --socket=/opt/mysql3306/data/mysql.sock  D=test,t=employee1 --alter "partition by RANGE COLUMNS (update_time)(PARTITION P202003 VALUES LESS THAN ('2020-04-01'),PARTITION P202004 VALUES LESS THAN ('2020-05-01'),PARTITION P202005 VALUES LESS THAN ('2020-06-01'), PARTITION PMAX VALUES LESS THAN  MAXVALUE  ) "  --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200"

5.以下为执行日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
[root@VM_24_101_centos ~]# mysql test -e "alter table employee1 modify id int(10) unsigned NOT NULL,algorithm=inplace,lock=none;"
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@VM_24_101_centos ~]# mysql test -e "alter table employee1 drop primary key,ADD PRIMARY KEY (update_time,id),algorithm=inplace,lock=none;"
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@VM_24_101_centos ~]# mysql test -e "show create table employee1\G"
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Table: employee1
Create Table: CREATE TABLE `employee1` (
`id` int(10) unsigned NOT NULL,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(300) NOT NULL DEFAULT '',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`update_time`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
[root@VM_24_101_centos ~]# pt-online-schema-change --user=root --password=111111 --socket=/opt/mysql3306/data/mysql.sock D=test,t=employee1 --alter "partition by RANGE COLUMNS (update_time)(PARTITION P202003 VALUES LESS THAN ('2020-04-01'),PARTITION P202004 VALUES LESS THAN ('2020-05-01'),PARTITION P202005 VALUES LESS THAN ('2020-06-01'), PARTITION PMAX VALUES LESS THAN MAXVALUE ) " --recursion-method=none --no-check-replication-filters --alter-foreign-keys-method auto --print --execute --critical-load="Threads_running:200" --charset=utf8mb4
No slaves found. See --recursion-method if host VM_24_101_centos has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
analyze_table, 10, 1
copy_rows, 10, 0.25
create_triggers, 10, 1
drop_triggers, 10, 1
swap_tables, 10, 1
update_foreign_keys, 10, 1
No foreign keys reference `test`.`employee1`; ignoring --alter-foreign-keys-method.
Altering `test`.`employee1`...
Creating new table...
CREATE TABLE `test`.`_employee1_new` (
`id` int(10) unsigned NOT NULL,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(300) NOT NULL DEFAULT '',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`update_time`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Created new table test._employee1_new OK.
Altering new table...
ALTER TABLE `test`.`_employee1_new` partition by RANGE COLUMNS (update_time)(PARTITION P202003 VALUES LESS THAN ('2020-04-01'),PARTITION P202004 VALUES LESS THAN ('2020-05-01'),PARTITION P202005 VALUES LESS THAN ('2020-06-01'), PARTITION PMAX VALUES LESS THAN MAXVALUE )
Altered `test`.`_employee1_new` OK.
2018-09-21T11:19:14 Creating triggers...
2018-09-21T11:19:14 Created triggers OK.
2018-09-21T11:19:14 Copying approximately 10000 rows...
INSERT LOW_PRIORITY IGNORE INTO `test`.`_employee1_new` (`id`, `employeeid`, `employeename`, `update_time`) SELECT `id`, `employeeid`, `employeename`, `update_time` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`update_time` > ?) OR (`update_time` = ? AND `id` >= ?)) AND ((`update_time` < ?) OR (`update_time` = ? AND `id` <= ?)) LOCK IN SHARE MODE /*pt-online-schema-change 16863 copy nibble*/
SELECT /*!40001 SQL_NO_CACHE */ `update_time`, `update_time`, `id` FROM `test`.`employee1` FORCE INDEX(`PRIMARY`) WHERE ((`update_time` > ?) OR (`update_time` = ? AND `id` >= ?)) ORDER BY `update_time`, `id` LIMIT ?, 2 /*next chunk boundary*/
2018-09-21T11:19:15 Copied rows OK.
2018-09-21T11:19:15 Analyzing new table...
2018-09-21T11:19:15 Swapping tables...
RENAME TABLE `test`.`employee1` TO `test`.`_employee1_old`, `test`.`_employee1_new` TO `test`.`employee1`
2018-09-21T11:19:15 Swapped original and new tables OK.
2018-09-21T11:19:15 Dropping old table...
DROP TABLE IF EXISTS `test`.`_employee1_old`
2018-09-21T11:19:15 Dropped old table `test`.`_employee1_old` OK.
2018-09-21T11:19:15 Dropping triggers...
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_del`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_upd`
DROP TRIGGER IF EXISTS `test`.`pt_osc_test_employee1_ins`
2018-09-21T11:19:15 Dropped triggers OK.
Successfully altered `test`.`employee1`.
[root@VM_24_101_centos ~]# mysql test -e "show create table employee1\G"
mysql: [Warning] Using a password on the command line interface can be insecure.
*************************** 1. row ***************************
Table: employee1
Create Table: CREATE TABLE `employee1` (
`id` int(10) unsigned NOT NULL,
`employeeid` int(10) unsigned NOT NULL COMMENT '0',
`employeename` varchar(300) NOT NULL DEFAULT '',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`update_time`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
/*!50500 PARTITION BY RANGE COLUMNS(update_time)
(PARTITION P202003 VALUES LESS THAN ('2020-04-01') ENGINE = InnoDB,
PARTITION P202004 VALUES LESS THAN ('2020-05-01') ENGINE = InnoDB,
PARTITION P202005 VALUES LESS THAN ('2020-06-01') ENGINE = InnoDB,
PARTITION PMAX VALUES LESS THAN (MAXVALUE) ENGINE = InnoDB) */
[root@VM_24_101_centos ~]#

注意事项

慎用参数–set-vars=’sql_log_bin=0’

生产环境,当表中数据量过大,如果遇到添加索引的需求,部分人可能会采用分别在主库和从库加索引的方式,当在主库添加索引的时候,使用了–set-vars=’sql_log_bin=0’参数,可能会导致主从中断。

原因:

因为 –set-vars=’sql_log_bin=0’的原因,创建表的DDL语句,无法通过binlog在从库建表,所以临时表主库存在,从库不存在。

由于增量数据通过触发器将对原表的操作同步到临时表中,此时,对原表的操作会产生binlog,从库在应用此部分的binlog时会报错,提示表不存在。

建议:

低峰期在主库执行,不要使用参数–set-vars=’sql_log_bin=0’。

谨慎在从库执行(ROW格式)

对于生产大表加字段,有种方式是:先在从库加字段,主从切换,再在原主上加字段,可能有效的减小加字段对生产的影响。但是,当复制格式为ROW时,可能会出现复制中断的情况。

原因:

当使用pt-online-schema-change在从库执行时,会先创建临时表,在临时表上做DDL变更,然后,通过创建触发器的方式同步增量数据。由于从库同步是应用主库的binlog,从库创建的触发器就不会产生效果,增量数据也就不会同步到临时表,最后在做了临时表和原表的原子重命名后,会丢失数据,复制就会报1032错误,找不到行记录,复制中断。

建议:

低峰期在主库执行,不要在从库执行。

自增锁

模拟一种场景:

1.创建测试表,创建测试临时表,同时在原表上创建更新的触发器,模拟pt-online-schema-change,由于是人工模拟,在触发器中添加了sleep(5)。

分别在session1更新,session2执行insert low_priority ignore into(session1执行后的5s内执行)

1
2
3
4
5
6
7
8
create table t1 (id int auto_increment primary key,name varchar(10));
insert into t1 select null,'A1';
insert into t1 select null,'B2';
insert into t1 select null,'C3';
insert into t1 select null,'D4';
insert into t1 select null,'E5';
create table _t1_new like t1;
alter table _t1_new engine = innodb;

2.session1:更新原表

1
2
begin;
update t1 set name = 'DDD4' where id = 4;

3.session2:插入数据到临时表(session1执行后的5s内执行)

1
2
begin;
insert low_priority ignore into _t1_new(id,name) select id,name from t1 where id > 2 and id <5 lock in share mode;

4.session1出现死锁

1
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

5.以下为mysql5.7,rc隔离级别的测试案例

session1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
[root@localhost][test][08:44:06]> create table t1 (id int auto_increment primary key,name varchar(10));
Query OK, 0 rows affected (0.02 sec)

[root@localhost][test][08:44:07]> insert into t1 select null,'A1';
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:07]> insert into t1 select null,'B2';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:07]> insert into t1 select null,'C3';
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:07]> insert into t1 select null,'D4';
Query OK, 1 row affected (0.01 sec)
Records: 1 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:07]> insert into t1 select null,'E5';
Query OK, 1 row affected (0.00 sec)
Records: 1 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:07]> create table _t1_new like t1;
Query OK, 0 rows affected (0.02 sec)

[root@localhost][test][08:44:07]> alter table _t1_new engine = innodb;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:08]> delimiter //
[root@localhost][test][08:44:11]> create trigger pt_osc_test_t1_upd after update on test.t1 for each row begin declare x int; set x = sleep(5); delete IGNORE from test._t1_new where !(OLD.`id` <=> NEW.`id`) and test._t1_new.id <=> OLD.`id`;replace into test._t1_new (`id`, `name`) values (NEW.`id`, NEW.`name`);END//
Query OK, 0 rows affected (0.00 sec)

[root@localhost][test][08:44:11]> delimiter ;
[root@localhost][test][08:44:16]> select * from t1;
+----+------+
| id | name |
+----+------+
| 1 | A1 |
| 2 | B2 |
| 3 | C3 |
| 4 | D4 |
| 5 | E5 |
+----+------+
5 rows in set (0.00 sec)

[root@localhost][test][08:44:16]>
[root@localhost][test][08:44:25]> begin;
Query OK, 0 rows affected (0.00 sec)

[root@localhost][test][08:44:26]> update t1 set name = 'DDD4' where id = 4;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
[root@localhost][test][08:44:31]>
[root@localhost][test][08:44:34]> show engine innodb status\G
*************************** 1. row ***************************
Type: InnoDB
Name:
Status:
=====================================
2020-05-28 08:44:38 0x7fa64c23e700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 28 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 26639 srv_active, 0 srv_shutdown, 395877 srv_idle
srv_master_thread log flush and writes: 422516
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 10330755
OS WAIT ARRAY INFO: signal count 23257527
RW-shared spins 0, rounds 26575782, OS waits 8009821
RW-excl spins 0, rounds 48751926, OS waits 641098
RW-sx spins 299480, rounds 8787074, OS waits 284218
Spin rounds per wait: 26575782.00 RW-shared, 48751926.00 RW-excl, 29.34 RW-sx
------------------------
LATEST DETECTED DEADLOCK
------------------------
2020-05-28 08:44:31 0x7fa64c23e700
*** (1) TRANSACTION:
TRANSACTION 4481653631, ACTIVE 1 sec fetching rows
mysql tables in use 2, locked 2
LOCK WAIT 5 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 36589, OS thread handle 140352217921280, query id 207646444 localhost root Sending data
insert low_priority ignore into _t1_new(id,name) select id,name from t1 where id > 2 and id <5 lock in share mode
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 346 page no 3 n bits 80 index PRIMARY of table `test`.`t1` trx id 4481653631 lock mode S locks rec but not gap waiting
Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000004; asc ;;
1: len 6; hex 00010b209b7e; asc ~;;
2: len 7; hex 580004fd7008c3; asc X p ;;
3: len 4; hex 44444434; asc DDD4;;

*** (2) TRANSACTION:
TRANSACTION 4481653630, ACTIVE 5 sec setting auto-inc lock, thread declared inside InnoDB 5000
mysql tables in use 2, locked 2
4 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 2
MySQL thread id 36587, OS thread handle 140352218720000, query id 207646446 localhost root update
replace into test._t1_new (`id`, `name`) values (NEW.`id`, NEW.`name`)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 346 page no 3 n bits 72 index PRIMARY of table `test`.`t1` trx id 4481653630 lock_mode X locks rec but not gap
Record lock, heap no 7 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000004; asc ;;
1: len 6; hex 00010b209b7e; asc ~;;
2: len 7; hex 580004fd7008c3; asc X p ;;
3: len 4; hex 44444434; asc DDD4;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
TABLE LOCK table `test`.`_t1_new` trx id 4481653630 lock mode AUTO-INC waiting
*** WE ROLL BACK TRANSACTION (2)
------------
TRANSACTIONS
------------
Trx id counter 4481653637
Purge done for trx's n:o < 4481653637 undo n:o < 0 state: running but idle
History list length 60
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421829777725264, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421829777727088, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 4481653631, ACTIVE 8 sec
6 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 2
MySQL thread id 36589, OS thread handle 140352217921280, query id 207646444 localhost root
--------
FILE I/O
--------
...

session2:

1
2
3
4
5
6
7
8
[root@localhost][test][08:44:29]> begin;
Query OK, 0 rows affected (0.00 sec)

[root@localhost][test][08:44:30]> insert low_priority ignore into _t1_new(id,name) select id,name from t1 where id > 2 and id <5 lock in share mode;
Query OK, 2 rows affected (1.06 sec)
Records: 2 Duplicates: 0 Warnings: 0

[root@localhost][test][08:44:31]>

分析:

1.原表更新和触发器触发后的临时表更新在同一事务内,通过查看binlog可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#180920 8:58:06 server id 1 end_log_pos 124 CRC32 0x9dc68bf8 Start: binlog v 4, server v 8.0.18 created 180920 8:58:06
# Warning: this binlog is either in use or was not closed properly.
BINLOG '
ngzPXg8BAAAAeAAAAHwAAAABAAQAOC4wLjE4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAEwANAAgAAAAABAAEAAAAYAAEGggAAAAICAgCAAAACgoKKioAEjQA
CgH4i8ad
'/*!*/;
# at 124
#180920 8:58:06 server id 1 end_log_pos 195 CRC32 0x16f7d4d2 Previous-GTIDs
# 3e5359e7-6ca7-11e8-b856-525400931d92:470870-495654
# at 195
#180920 8:58:21 server id 1 end_log_pos 274 CRC32 0x5e21ec93 GTID last_committed=0 sequence_number=1 rbr_only=yes original_committed_timestamp=1590627501692250 immediate_commit_timestamp=1590627501692250 transaction_length=474
/*!50718 SET TRANSACTION ISOLATION LEVEL READ COMMITTED*//*!*/;
# original_commit_timestamp=1590627501692250 (2020-05-28 08:58:21.692250 CST)
# immediate_commit_timestamp=1590627501692250 (2020-05-28 08:58:21.692250 CST)
/*!80001 SET @@session.original_commit_timestamp=1590627501692250*//*!*/;
/*!80014 SET @@session.original_server_version=80018*//*!*/;
/*!80014 SET @@session.immediate_server_version=80018*//*!*/;
SET @@SESSION.GTID_NEXT= '3e5359e7-6ca7-11e8-b856-525400931d92:495655'/*!*/;
# at 274
#180920 8:58:15 server id 1 end_log_pos 358 CRC32 0xedf75521 Query thread_id=781 exec_time=0 error_code=0
SET TIMESTAMP=1590627495/*!*/;
SET @@session.pseudo_thread_id=781/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1/*!*/;
SET @@session.sql_mode=1168113696/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=255/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
/*!80011 SET @@session.default_collation_for_utf8mb4=255*//*!*/;
BEGIN
/*!*/;
# at 358
#180920 8:58:15 server id 1 end_log_pos 422 CRC32 0x3e869251 Rows_query
# update t1 set name = 'DDD4' where id = 4
# at 422
#180920 8:58:15 server id 1 end_log_pos 478 CRC32 0x6c91a26c Table_map: `test`.`t1` mapped to number 287
# at 478
#180920 8:58:15 server id 1 end_log_pos 539 CRC32 0xe153a38c Table_map: `test`.`_t1_new` mapped to number 286
# at 539
#180920 8:58:15 server id 1 end_log_pos 593 CRC32 0xa2ff1eed Update_rows: table id 287
# at 593
#180920 8:58:15 server id 1 end_log_pos 638 CRC32 0xd6d9a8bf Write_rows: table id 286 flags: STMT_END_F

BINLOG '
pwzPXh0BAAAAQAAAAKYBAACAACh1cGRhdGUgdDEgc2V0IG5hbWUgPSAnRERENCcgd2hlcmUgaWQg
PSA0UZKGPg==
pwzPXhMBAAAAOAAAAN4BAAAAAB8BAAAAAAEABHRlc3QAAnQxAAIDDwIoAAIBAQACA/z/AGyikWw=
pwzPXhMBAAAAPQAAABsCAAAAAB4BAAAAAAEABHRlc3QAB190MV9uZXcAAgMPAigAAgEBAAID/P8A
jKNT4Q==
pwzPXh8BAAAANgAAAFECAAAAAB8BAAAAAAAAAgAC//8ABAAAAAJENAAEAAAABERERDTtHv+i
pwzPXh4BAAAALQAAAH4CAAAAAB4BAAAAAAEAAgAC/wAEAAAABERERDS/qNnW
'/*!*/;
### UPDATE `test`.`t1`
### WHERE
### @1=4 /* INT meta=0 nullable=0 is_null=0 */
### @2='D4' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */
### SET
### @1=4 /* INT meta=0 nullable=0 is_null=0 */
### @2='DDD4' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */
### INSERT INTO `test`.`_t1_new`
### SET
### @1=4 /* INT meta=0 nullable=0 is_null=0 */
### @2='DDD4' /* VARSTRING(40) meta=40 nullable=1 is_null=0 */
# at 638
#180920 8:58:21 server id 1 end_log_pos 669 CRC32 0x4847dd4b Xid = 61574
COMMIT/*!*/;

其中,最后面的UPDATE test.t1和INSERT INTO test._t1_new可以看出来。

2.session1执行update t1 set name = ‘DDD4’ where id = 4;时,对应的show engine innodb status中的TRANSACTION 2

此时,TRANSACTION 2持有t1表id=4的x锁,由于触发器的原因,t1和_t1_new的更新在同一事务内,等待获取_t1_new表的auto-inc lock,等待的语句为:replace into test._t1_new (id, name) values (NEW.id, NEW.name)

3.session2执行insert low_priority ignore into _t1_new(id,name) select id,name from t1 where id > 2 and id <5 lock in share mode;时,对应的show engine innodb status中的TRANSACTION 1

此时,TRANSACTION 1持有_t1_new的auto-inc lock,等待获取t1的记录锁

4.session1和session2产生了死锁,回滚了TRANSACTION 2

注意:

MySQL8.0在上述实验场景中,并不会出现死锁。

总结

pt-online-schema-change是一个很优秀的在线DDL工具,在gh-ost出现之前,广泛应用于生产。同时,为了更好的使用他提高工作效率,需要了解其应用场景与内部工作原理,避免踩到坑。

MySQL TimeOut分析

发表于 2018-08-05 | 分类于 MySQL
字数统计: | 阅读时长 ≈

引言

在做日常mysql数据库维护过程,时长会遇到关于mysql timeout的相关报错,了解mysql的连接过程和常见的错误类型,知己知彼,可以更好去运维mysql,做到心中有数。

MySQL连接参数

和网络超时相关的参数,这里做下简单说明:

  • interactive_timeout

    1
    服务器在关闭之前等待交互式连接的超时时间,交互式客户端定义为在mysql_real_connect()中使用CLIENT_INTERACTIVE选项的客户端。像mysql客户端是交互式客户端。
  • wait_timeout

    1
    服务器关闭非交互式连接之前等待活动的秒数,在线程启动时,根据全局wait_timeout值或全局interactive_timeout值初始化会话wait_timeout值,取决于客户端类型(由mysql_real_connect()的连接选项CLIENT_INTERACTIVE定义)。像java使用的jdbc连接,php使用pdo连接等都是非交互式连接。
  • net_read_timeout

    1
    在终止读之前,从一个连接获得数据而等待的时间秒数;当服务正在从客户端读取数据时,net_read_timeout控制何时超时。例如load data local infile语句。
  • net_write_timeout

    1
    在终止写之前,等待多少秒把block写到连接,当服务正在写数据到客户端时,net_write_timeout控制何时超时
  • connect_timeout

    1
    在连接认证阶段的网络交互超时时间(ref login_connection)。

MySQL连接过程

其中

  • connect_timeout 在获取连接阶段(authenticate)起作用
  • interactive_timeout和wait_timeout 在连接空闲阶段(sleep)起作用
  • net_read_timeout和net_write_timeout 则是在连接繁忙阶段(query)起作用。

    mysql通信协议

    mysql 客户端和服务端之间的通信协议是”半双工”, 在任何一个时刻,要么是由服务端向客户端发送数据,要么是由客户端向服务端发送数据,这两个动作不能同时发生。参数max_allowed_packet在做导入数据的时候,net_buffer_length在缓冲数据的时候就很重要了。

    connect_timeout

    1
    2
    Connect_timeout
    The number of seconds that the mysqld server waits for a connect packet before responding with Bad handshake

MySQL连接一次连接需求经过6次“握手”方可成功,任意一次“握手”失败都有可能导致连接失败,前三次握手可以简单理解为TCP建立连接所必须的三次握手,MySQL无法控制,更多的受制于不TCP协议的不同实现,后面的三次握手过程超时与connect_timeout有关。
image

建立连接步骤:

  1. 客户端向DB发起TCP握手
  2. 三次握手成功,由DB发送HandShake信息,这个Packet里面包含了MySql的能力、加密seed等信息。
  3. 客户端根据HandShake包里面的加密seed对MySql登录密码进行摘要后,构造Auth认证包发送给DB。
  4. DB接收到客户端发过来的Auth包后会对密码摘要进行比对,从而确认是否能够登录。如果能,则发送Okay包返回。
  5. 客户端与DB的连接至此完毕。

net_read_timeout && net_write_timeout

当connect建立后,读取命令的流程如下:

  1. read_packet 通过函数my_net_read读取数据包,如果一次读不完成,则调用函数net_read_raw_loop进行循环读取,读取的超时由wait_timeout决定
    1) 会先读取packet header,一个普通的packet header包含4个字节,压缩协议下则另外再加3个字节
    2) 再从packet header中提取剩下的packet长度,继续从socket读取
  2. vio_read_buff vio封装了所有对socket的操作,当read_packet完成后,进入到vio相关的函数做封装处理,包括vio_init, vio_read_buffer,vio_read等。
  3. my_net_write mysql通过read_packet做过相关处理后,通过my_net_write函数将数据先拷贝到NET缓冲区,当长度大于MAX_PACKET_LENGTH(即4MB-1字节)会对Packet进行拆分成多个packet。每个Packet的头部都会留4个字节,其中:1~3字节,存储该packet的长度,第4个字节存储当前的packet的序号,每存储一次后递增net->pkt_nr。
    每个Net对象有一个Buff(net->buff),即将发送的数据被拷贝到这个buffer中,当Buffer满时需要立刻发出到客户端。如果Buffer足够大,则只做memcpy操作。net->write_pos被更新到写入结束的位置偏移量 (net_write_buff)。
    如果一次写入的数据被拆分成多个Packet,那么net->pkt_nr也对应的递增多次. pkt_nr的作用是在客户端解析时,防止包发送乱序。
  4. net_flush 在my_net_write函数中,如果net->buff不够用,已经会做网络写了,net_flush最终保证所有在buff中的数据被写到网络。
  5. net_write_raw_loop 当packet准备好发送后,调用函数net_write_raw_loop开始进行数据发送。
    1) 在发送模式受vio->write_timeout影响(通过参数net_write_timeout控制);当该参数被设置成大于等于0时,使用非阻塞模式send数据包(MSG_DONTWAIT)
    2) 若网络发送被中断(EINTR),会去尝试重传
    3) 使用非阻塞模式send,每次并不保证数据全部发送完毕,因此需要循环的调用直到所有的数据都发送完毕
    4) 当输出缓冲区满时,获得错误码EWOULDBLOCK/EAGAIN,则阻塞等待(vio_socket_io_wait),最大等待时间为net_write_timeout,超时则返回错误

interactive_timeout && wait_timeout

当query处理完成后,连接会进入到空闲阶段,此时受interactive_timeout和wait_timeout控制。关于interactive_timeout和wait_timeout,session和global有继承关系,可以自己测试,这里有篇文章,测试的很详细。

MySQL超时类型

Got timeout reading communication packets

连接超时

设置interactive_timeout和wait_timeout,使用mysql客户端连接,等待设置的超时时间后被mysql kill掉,在日志观察详情

准备

设置interactive_timeout和wait_timeout

image

观察日志

通过观察error log查看连接是否断开

image

其中,连接的thread_id为2885,超过10s后断开,出现日志详情

1
2018-08-03T10:11:48.650754+08:00 2885 [Note] [MY-010914] [Server] Aborted connection 2885 to db: 'unconnected' user: 'root' host: 'localhost' (Got timeout reading communication packets).

其他

其他常见的错误,可以参考官方文档,有详情的介绍,也可根据实际测试结果来验证。

Got an error reading communication packets

客户端异常退出(数据包异常)

使用pt-kill工具监控mysql的语句,通过在mysql确认pt-kill守护进程存在,kill掉此进程,观察日志的详情

准备
  1. pt-kill安装
    安装pt-kill工具,测试机使用ubuntu 16.04机器,直接使用以下命令:
    1
    2
    wget -c https://www.percona.com/downloads/percona-toolkit/3.0.10/binary/debian/xenial/x86_64/percona-toolkit_3.0.10-1.xenial_amd64.deb
    dpkg -i percona-toolkit_3.0.10-1.xenial_amd64.deb
pt-kill测试
  1. 运行pt-kill进程
    命令:
    1
    pt-kill --no-version-check --host localhost --port 3306 --user root --password 111111 --socket /opt/mysql/data/mysql.sock --match-host="127.0.0.1|localhost" --match-user="root" --match-db="sysbench|test" --match-command="Query" --match-state="statistics" --match-info="(?i-xsm:select)" --victim all --interval 1  --kill-query --daemonize --print --log=/tmp/pt-kill.log

image

  1. 查看pt-kill进程

image

  1. 查看mysql相关pt-kill进程

image

  1. Kill掉pt-kill进程

image

  1. 观察日志
    1
    2
    2018-08-02T15:21:32.254962+08:00 62476 [Note] [MY-010914] [Server] Aborted connection 62476 to db: 'unconnected' user: 'root' host: 'localhost' (Got an error readin
    g communication packets).

image

网络错误

准备

使用linux自带的tc命令篡改数据包,设置网卡随机产生30%的损坏数据包,也可使用tc的其他场景,比如数据包重复等待去做各种测试,这里仅使用2种场景分别测试损坏数据包时,是否产生Got an error writing communication packets的情况

  1. 设置tc数据包修改: 命令,由于测试的机器包括多块网卡,这里全部设置:
    1
    2
    3
    tc  qdisc  add  dev  eth0  root  netem  corrupt  30% 
    tc qdisc add dev eth1 root netem corrupt 30%
    tc qdisc add dev lo root netem corrupt 30%

image

image

  1. ping结果查看: 通过ping查看,tc的设置已经生效,出现了损坏数据包

image

sysbench测试

通过在sysbench压测过程中,人工设置,模拟网络出现问题的情况,观察日志

  1. sysbench压测
    1
    /usr/local/bin/sysbench --db-driver=mysql --mysql-socket=/opt/mysql/data/mysql.sock --tables=1 --table_size=1000000 --report-interval=10 --time=3600 --mysql-port=3306 --mysql-user=root --mysql-password=111111 --mysql-db=sysbench --mysql-host=10.30.167.90 --max-requests=0 /usr/local/share/sysbench/oltp_read_write.lua --threads=4 run

压测结果:

image

  1. 观察error log

已经出现了Got an error writing communication packets相关告警信息

1
2
3
4
2018-08-02T16:35:51.858793+08:00 65764 [Note] [MY-010914] [Server] Aborted connection 65764 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error writing communication packets).
2018-08-02T16:35:51.899059+08:00 65767 [Note] [MY-010914] [Server] Aborted connection 65767 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error writing communication packets).
2018-08-02T16:35:51.899260+08:00 65765 [Note] [MY-010914] [Server] Aborted connection 65765 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error writing communication packets).
2018-08-02T16:35:51.899339+08:00 65766 [Note] [MY-010914] [Server] Aborted connection 65766 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error writing communication packets).

image

php测试

通过以运行php代码的形式来访问数据,人工设置,模拟网络出现问题的情况,观察日志

  1. php代码运行
    这里以互联网在线模拟为例子,地址:https://www.dooccn.com/php/

代码详情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
header('content-type:text/html;charset=utf-8');
try {
$db = new PDO('mysql:host=118.190.67.67;dbname=mysql', 'root', 'xxx');
//查询
$rows = $db->query('SELECT sleep(100)')->fetch(PDO::FETCH_ASSOC);
$rs = array();
foreach($rows as $row) {
$rs[] = $row;
}
$db = null;
} catch (PDOException $e) {
print "Error!: " . $e->getMessage() . "<br/>";
die();
}
print_r($rs);

  1. 观察error log

已经出现了Got an error writing communication packets相关告警信息

1
2018-08-02T16:37:31.183490+08:00 66089 [Note] [MY-010914] [Server] Aborted connection 66089 to db: 'mysql' user: 'root' host: '106.14.17.222' (Got an error reading communication packets).

image

max_prepared_stmt_count

  • max_prepared_stmt_count
    此参数限制了同一时间在mysqld上所有session中prepared 语句的上限。一般做压力测试的prepare阶段可以会遇到此参数设置较小的问题,同时日志中会返回Got an error reading communication packets的错误,这里不再测试,在做压力测试时,需要将此参数设置大些。
准备
  1. 设置max_prepared_stmt_count

image

sysbench测试
  1. 运行压力测试

    1
    /usr/local/bin/sysbench --db-driver=mysql --mysql-socket=/opt/mysql/data/mysql.sock --tables=1 --table_size=1000000 --report-interval=10 --time=3600 --mysql-port=3306 --mysql-user=root --mysql-password=111111 --mysql-db=sysbench --mysql-host=10.30.167.90 --max-requests=0 /usr/local/share/sysbench/oltp_read_write.lua --threads=4 run
  2. 查看sysbench日志

在11.04的时候运行的压力测试

image

  1. 查看mysql log
    1
    2
    3
    2018-08-03T11:04:07.696809+08:00 5366 [Note] [MY-010914] [Server] Aborted connection 5366 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error reading communication packets).
    2018-08-03T11:04:07.696884+08:00 5365 [Note] [MY-010914] [Server] Aborted connection 5365 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error reading communication packets).
    2018-08-03T11:04:07.696959+08:00 5364 [Note] [MY-010914] [Server] Aborted connection 5364 to db: 'sysbench' user: 'root' host: 'localhost' (Got an error reading communication packets).

image

其他

其他常见的错误,可以参考官方文档,有详情的介绍,也可根据实际测试结果来验证。

Lost connection to MySQL server during query

超时connect_timeout

使用linux自带的tc命令设置网络延迟,达到测试所需要的效果

准备
  1. 设置tc数据包延迟:命令,由于测试的机器包括多块网卡,这里全部设置:
    1
    2
    tc qdisc add dev eth1 root netem delay 11000ms
    tc qdisc add dev eth0 root netem delay 11000ms

image

登录连接测试

通过远程连接其他mysql服务器,查看登录效果,超过了默认connect_timeout的10s,报Lost connection to MySQL server during query错误

image

客户端异常退出(数据包正常)

开启2个session窗口,连接同一个mysql实例,第1个session开启查询,第2个session去kill掉第一个session

准备
  1. Session1开启查询

image

手动kill
  1. Session2查看processlist

image

  1. session2 手动kill session1连接

image

  1. session1查看查询

image

其他

其他常见的错误,可以参考官方文档,有详情的介绍,也可根据实际测试结果来验证。

MySQL server has gone away

MySQL server has gone away

设置mysql的interactive_timeout,使用mysql客户端开启一个窗口

准备
  1. 设置interactive_timeout的值

image

执行查询
  1. 在同一窗口内执行查询select sleep(10)

image

  1. 等待超过interactive_timeout时间后,再次执行查询

已经出现的错误日志:MySQL server has gone away

image

其他

其他常见的错误,可以参考官方文档,有详情的介绍,也可根据实际测试结果来验证。

参考

1
2
3
4
5
http://mysql.taobao.org/monthly/2016/07/04/
http://mysql.taobao.org/monthly/2017/05/04/
http://www.importnew.com/29055.html
https://www.cnblogs.com/cchust/p/5025880.html
https://my.oschina.net/alchemystar/blog/833598

MySQL启动配置文件搜索顺序解析

发表于 2018-07-20 | 分类于 MySQL
字数统计: | 阅读时长 ≈

引言

MySQL数据库支持从配置文件中读取启动选项,不用每次启动时在命令行输入,方便管理。很多选项都是以纯文本的形式存在,可以用任何的编辑器打开编辑。.mylogin.cnf文件除外,此文件包含了登录路径的选项,使用mysql_config_editor程序去创建的加密文件。

搜索顺序

MySQL默认情况下是按照一定的顺序去搜索具体路径的配置文件,并且在windows和linux的操作顺序不一样。
搜索顺序,按照下面表格中文件名从上往下,如果存在相同的选项,最后一个出现的选项有最高的优先级。参考官网的解释:”Options are processed in order, so if an option is specified multiple times, the last occurrence takes precedence.”

Windows

在windows上,MySQL按照指定的顺序从下表中显示的文件中读取启动选项

文件名 描述
%WINDIR%\my.ini, %WINDIR%\my.cnf 全局
C:\my.ini, C:\my.cnf 全局
BASEDIR\my.ini, BASEDIR\my.cnf 全局
defaults-extra-file 如果存在—default-extra-file
%APPDATA%\MySQL.mylogin.cnf Login选项,仅适用于client
1
2
3
4
其中,
WINDIR表示Windows目录的位置,例如C:\Windows
APPDIR表示Windows应用程序数据目录
BASEDIR表示MySQL的安装目录

Linux or Unix

在Linux或Unix等类Unix系统上,MySQL按照指定的顺序从下表中显示的文件中读取启动选项

文件名 描述
/etc/my.cnf 全局
/etc/mysql/my.cnf 全局
SYSCONFDIR/my.cnf 全局
$MYSQL_HOME/my.cnf 仅适用于server
defaults-extra-file 如果存在—default-extra-file
~/.my.cnf 用户的选项
~/.mylogin.cnf Login选项,仅适用于client
1
2
3
4
其中,
~表示当前用户的主目录
SYSCONFIGDIR表示在构建MySQL时使用SYSCONFIGDIR选项CMake的指定目录
MySQL_HOME是一个环境变量,如果没有设置,并使用mysqld_safe启用MySQL,这时MySQL的安装目录就是其BASEDIR

实验

生产环境一般多使用linux系统,在单实例和多实例启动时,my.cnf加载的顺序也存在不一样的情况,这里分别进行测试,对结果进行说明

单实例

在不考虑SYSCONDIR,同时BASEDIR为/usr/local/mysql的情况下,从上面的搜索顺序来看,MySQL会依次加载以下的文件:

  • /etc/my.cnf
  • /etc/mysql/my.cnf
  • /usr/local/mysql/my.cnf
  • ~/.my.cnf
    也就是说MySQL会找/etc/my.cnf这个文件,如果此文件不存在,继续找/etc/mysql/my.cnf这个文件,依此类推,最后是读取–defaults-file加载的文件,如果最后的文件也不存在,通过mysqld的帮助可以看出来:

image

/etc/my.cnf

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf中只存在/etc/my.cnf时,很明显MySQL是加载的/etc/my.cnf

image

/etc/my.cnf && /etc/mysql/my.cnf

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf中只存在/etc/my.cnf和/etc/mysql/my.cnf时,很明显MySQL是加载的/etc/mysql/my.cnf

image

/etc/my.cnf && /etc/mysql/my.cnf && /usr/local/mysql/my.cnf

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf中存在/etc/my.cnf和/etc/mysql/my.cnf和/usr/local/mysql/my.cnf时,很明显MySQL是加载的/usr/local/mysql/my.cnf

image

/etc/my.cnf && /etc/mysql/my.cnf && /usr/local/mysql/my.cnf && ~/.my.cnf

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,很明显MySQL是加载的~/my.cnf

image

defaults-file

当指定–defaults-file为/tmp/my.cnf时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,很明显MySQL是加载的/tmp/my.cnf

image

多实例

多实例的启动通常使用mysqld_multi脚本,此脚本也支持–defaults-file选项。从mysqld_multi的官档上可以看出,mysqld_multi支持[mysqldN]的标签,其中N代表常量数字,此脚本是调用mysqld_safe命令是启动服务,所以支持[mysqld]标签做通用性配置文件,适用于多个实例启动时的通用配置,也可以使用[mysqldN],N代表特定端口做特殊性实例配置。

/etc/my.cnf && [mysqldN]

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,同时在[mysqld]中全部配置innodb_lock_wait_timeout,又在/etc/my.cnf加了一个[mysqld3306]标签,这时候,MySQL是加载的是/etc/my.cnf的[mysqld3306]标签配置文件

image

/etc/my.cnf && /etc/mysql/my.cnf && [mysqldN]

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,同时在[mysqld]中全部配置innodb_lock_wait_timeout,又在/etc/my.cnf和/etc/mysql/my.cnf加了一个[mysqld3306]标签,这时候,MySQL是加载的是/etc/mysql/my.cnf的[mysqld3306]标签配置文件

image

/etc/my.cnf && /etc/mysql/my.cnf &&/usr/local/mysql/my.cnf && [mysqldN]

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,同时在[mysqld]中全部配置innodb_lock_wait_timeout,又在/etc/my.cnf和/etc/mysql/my.cnf和/usr/local/mysql/my.cnf加了一个[mysqld3306]标签,这时候,MySQL是加载的还是/etc/my.cnf的[mysqld3306]标签配置文件

image

/etc/my.cnf && /etc/mysql/my.cnf &&/usr/local/mysql/my.cnf && ~/.mycf && [mysqldN]

当没有指定–defaults-file时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,同时在[mysqld]中全部配置innodb_lock_wait_timeout,又在全部配置了[mysqld3306]标签,这时候,MySQL是加载的是~/my.cnf的[mysqld3306]标签配置文件

image

defaults-file && [mysqldN]

当指定–defaults-file为/tmp/my.cnf时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,同时在了[mysqld]中全部配置innodb_lock_wait_timeout,又在全部配置了了[mysqld3306]标签,这时候,了MySQL是加载的是/tmp/my.cnf的[mysqld3306]标签配置文件

image

defaults-file

当指定–defaults-file为/tmp/my.cnf时,而且/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf全部存在时,同时在[mysqld]中全部配置innodb_lock_wait_timeout,全部未配置了[mysqld3306]标签,这时候,MySQL是加载的是~/my.cnf的配置文件而不是加载的/tmp/my.cnf的配置文件,说明mysqld_multi不加载除默认搜索的配置文件外的其他[mysqld]标签

image

启动方式

MySQL的启动包括多种,包括以下几种:

  • /usr/local/mysql/bin/mysqld –defaults-file=/etc/my.cnf –basedir=/usr/local/mysql –datadir=/opt/mysql/data –user=mysql
  • /usr/local/mysql/bin/mysqld_safe –defaults-file=/etc/my.cnf –basedir=/usr/local/mysql –datadir=/opt/mysql/data –user=mysql
  • /etc/init.d/mysqld start && service mysqld start
  • mysqld_multi start

    service mysqld start

    service mysqld start的启动有一个要注意的地方,它不会加载~/.my.cnf,实验如下,很明显,部service mysqld start启动方式不加载~/.my.cnf的参数

image

分析

1
由于linux的service启动服务的方式是调用的/sbin/service脚本来进行管理的,并且/sbin/service脚本是shell编写的,可以通过debug的模式来查看具体执行过程

image

1
2
3
4
5
通过上面的执行过程可以了解到,service mysqld start启动mysql的方式,实际最终调用的命令为:
env -i PATH=/sbin:/usr/sbin:/bin:/usr/bin TERM=linux SYSTEMCTL_IGNORE_DEPENDENCIES= SYSTEMCTL_SKIP_REDIRECT= /etc/init.d/mysqld start
此命令没有包含/home/mysql的环境变量,如果修改为以下命令,则可以加载~/.my.cnf
命令为:
env -i PATH=/sbin:/usr/sbin:/bin:/usr/bin HOME=/home/mysql TERM=linux SYSTEMCTL_IGNORE_DEPENDENCIES= SYSTEMCTL_SKIP_REDIRECT= /etc/init.d/mysqld start

解决方案

从脚本/sbin/service的最下面部分可以看出,具体的启动命令,如果想修改为让service mysqld start的启动方式也能搜索~/.my.cnf的参数,可以修改具体的脚本,添加了一个if else判断条件,如下:

1
2
3
4
5
6
7
8
9
将脚本/sbin/service的第81行的原始命令:
env -i PATH="$PATH" TERM="$TERM" SYSTEMCTL_IGNORE_DEPENDENCIES=${SYSTEMCTL_IGNORE_DEPENDENCIES} SYSTEMCTL_SKIP_REDIRECT=${SYSTEMCTL_SKIP_REDIRECT} "${SERVICEDIR}/${SERVICE}" ${ACTION} ${OPTIONS}
修改为
if [ "${SERVICE}" = "mysqld" ]; then
env -i PATH="$PATH" HOME=/home/mysql TERM="$TERM" SYSTEMCTL_IGNORE_DEPENDENCIES=${SYSTEMCTL_IGNORE_DEPENDENCIES} SYSTEMCTL_SKIP_REDIRECT=${SYSTEMCTL_SKIP_REDIREC
T} "${SERVICEDIR}/${SERVICE}" ${ACTION} ${OPTIONS}
else
env -i PATH="$PATH" TERM="$TERM" SYSTEMCTL_IGNORE_DEPENDENCIES=${SYSTEMCTL_IGNORE_DEPENDENCIES} SYSTEMCTL_SKIP_REDIRECT=${SYSTEMCTL_SKIP_REDIRECT} "${SERVICEDIR}/${SERVICE}" ${ACTION} ${OPTIONS}
fi

如下:

image

image
image

修改后的脚本启动一下测试,查看已经满足需求

image

mysqld_multi start

当mysqld_multi方式指定–defaults-file的参数文件时,不加载除默认搜索顺序以外的[mysqld]标签,也可以做下debug,mysqld_multi是perl脚本,可能通过perl -d script的方式进行调试,这里不再展示。

结论

通过以上的的测试,得出以下结论

1
2
3
4
1.	默认情况前三种启动方式下,[mysqld]标签以按照/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf,--defaults-file搜索顺序加载
2. mysqld_mutli启动方式,[mysqldN]标签也是按照/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf,--defaults-file搜索顺序加载。但是,[mysqld]标签只按照/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf搜索顺序加载,不加载--defaults-file的配置
3. service mysqld start启动方式,默认不加载~/.my.cnf文件,需要修改脚本才能让其支持搜索
4. 当找不到/etc/my.cnf,/etc/mysql/my.cnf,/usr/local/mysql/my.cnf,~/.my.cnf,--defaults-file的配置时,默认编译的mysql,参数是全部其默认值,数据目录保存在BASEDIR下的data目录。比如,BASEDIR为/usr/local/mysql,则DATADIR为/usr/local/mysql/data,上面没有实验这一项

pt-kill 使用教程

发表于 2018-07-17 | 分类于 工具
字数统计: | 阅读时长 ≈

介绍

pt-kill 是一个优秀的kill MySQL连接的工具,是percona toolkit的一部分,使用perl语言编写.简单高效.能够按照一定的规则匹配kill掉MySQL中的连接,达到有效保护服务的目的.

用途

pt-kill主要适用于MySQL实例中异常出现的会话导致整个实例负载过高,甚至出现不可用的情况下,进而影响应用,影响业务的场景.包括以下:

  • 手动维护MySQL连接,比如连接突然飙高
  • 自动维护MySQL连接,为了减小对业务的影响,结合定时任务,自动检测匹配超过一定阈值时间的查询,kill掉,保护实例的正常运行

原理

pt-kill的工作原理是连接到MySQL并从show processlist获取查询并进行过滤,然后执行kill或print操作

general_log

通过开启mysql的general_log,观察pt-kill执行的语句:

1
2
3
4
5
6
7
8
2018-07-16T08:25:26.764023+08:00	  412 Connect	root@localhost on  using TCP/IP
2018-07-16T08:25:26.839130+08:00 412 Query SHOW VARIABLES LIKE 'wait\_timeout'
2018-07-16T08:25:26.936724+08:00 412 Query SET SESSION wait_timeout=10000
2018-07-16T08:25:26.937101+08:00 412 Query SELECT @@SQL_MODE
2018-07-16T08:25:26.937323+08:00 412 Query SET @@SQL_QUOTE_SHOW_CREATE = 1/*!40101, @@SQL_MODE='NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'*/
2018-07-16T08:25:26.937534+08:00 412 Query SELECT @@server_id /*!50038 , @@hostname*/
2018-07-16T08:25:26.938054+08:00 412 Query SHOW FULL PROCESSLIST
2018-07-16T08:25:27.938917+08:00 412 Query SHOW FULL PROCESSLIST

用法

pt-kill [OPTIONS] [DSN]

pt-kill杀死MySQL连接。如果没有给出文件,pt-kill连接到MySQL然后从”SHOW PROCESSLIST”命令输出中获取查询。否则,就从包含有”SHOW PROCESSLIST”输出的一个或者多个文件中读取查询。如果文件是” - “,pt-kill从STDIN读取输入。

1.kill执行时间超过60s的查询

1
pt-kill --busy-time 60 --kill

2.条件同上,但是只print不kill

1
pt-kill --busy-time 60 --print

3.每10s检查sleep进程并kill

1
pt-kill --match-command Sleep --kill --victims all --interval 10

4.打印所有的登录进程

1
pt-kill --match-state login --print --victims all

5.查看当前进程列表中的查询匹配

1
2
mysql -e "SHOW PROCESSLIST" > proclist.txt
pt-kill --test-matching proclist.txt --busy-time 60 --print

风险

Percona Toolkit已经成熟,并得到了验证,并经过了充分测试,但所有数据库工具都可能对系统和数据库服务器造成风险,在使用此工具前注意:

  • 阅读工具的文档
  • 查看工具已知的bug列表
  • 在非生产服务器上测试
  • 备份生产服务器并验证备份有效性

描述

pt-kill从”SHOW PROCESSLIST”中捕捉查询,筛选,然后kill或者print,被称为“慢查询狙击手”。

pt-kill一般连接到MySQL从”SHOW PROCESSLIST”里获取查询。或者也可以从文件里面读取”SHOW PROCESSLIST”的输出结果。在这种情况下,pt-kill不会连接到MySQL,”–kill”选项也不会起作用。当读取文件时需要用”–print”选项,通过结合”–test-matching”选项读取文件,进行测试,为了保证匹配规则的正确性。特殊情况下要考虑,例如“不kill复制线程”等。

其中,选项–busy-time和–victims比较重要。

  • 首先,从”SHOW PROCESSLIST”里匹配对应的结果,比如–match-command规则去匹配command值, –busy-time去匹配time时间
  • 其次, –victims控制从每个类别里面匹配的哪些查询会被kill,默认最长时间的查询优先被kill

参数

至少需要指定一个–kill,–kill-query,–print,–execute-command和–stop,其中以下参数是互斥的

  • –any-busy-time和–each-busy-time
  • –kill和–kill-query
  • –daemonize和–test-matching

option选项

参数 描述
–ask-pass 连接MySQL时提示输入密码
–charset 默认字符集
–config 指定配置配置文件
–create-log-table 如果表–log-dsn不存在,则创建此表
–daemonize 后台进行进程
–database 连接的数据库
–defaults-file 从指定的文件读取mysql参数
–filter 丢弃Perl代码不返回true的事件
–group-by 将show processlist的匹配分组, 还可以匹配fingerprint查询,通过该查询对Info列中的SQL查询进行抽象
–help 显示帮助
–host 连接的主机
–interval 匹配查询的频繁,如果busy-time未指定,默认为30s
–log 守护进程日志输出文件
–log-dsn 将kill的结果存储到表中,传入的dsn必须包括库和表选项,表结构如下,也适用于–create-log-table选项
CREATE TABLE kill_log (
kill_id int(10) unsigned NOT NULL AUTO_INCREMENT,
server_id bigint(4) NOT NULL DEFAULT ‘0’,
timestamp DATETIME,
reason TEXT,
kill_error TEXT,
Id bigint(4) NOT NULL DEFAULT ‘0’,
User varchar(16) NOT NULL DEFAULT ‘’,
Host varchar(64) NOT NULL DEFAULT ‘’,
db varchar(64) DEFAULT NULL,
Command varchar(16) NOT NULL DEFAULT ‘’,
Time int(7) NOT NULL DEFAULT ‘0’,
State varchar(64) DEFAULT NULL,
Info longtext,
Time_ms bigint(21) DEFAULT ‘0’, # NOTE, TODO: currently not used
PRIMARY KEY (kill_id)
) DEFAULT CHARSET=utf8
–password 连接的密码
–pid 连接的pid
–port 连接的端口
–query-id 打印被kill的query id
–rds 连接到aws rds,rds不能使用–kill和–kill-query选项,改为用函数调用.
–kill使用CALL mysql.rds_kill(thread-id)
–kill-query使用CALL mysql.rds_kill_query(thread-id)
–run-time 退出前要运行多长时间,默认pt-kill会一直运行,直到进程被创建的–sentinel文件而触发终止
–sentinel 如果此文件存在,则pt-kill进程退出
–slave-user 连接到从库的用户
–slave-password 连接到从库的密码
–set-vars 设置mysql的变量,多个变量用逗号分割
–socket 连接的socket
–stop 通过创建–sentinel文件停止运行
–[no]strip-comments 从info查询中删除sql注释
–user 连接的用户名
–version 显示版本
–[no]version-check 检查Toolkit,MySQL和其他程序的最新版本
–victims 控制从每个类别里面匹配的哪些查询会被kill,包括以下:
Oldest:只kil最长的查询,防止误kill
All:kill所有
All-but-oldest:除最长的查询外,kill 所有
–wait-after-kill Kill一个查询后等待时长
–wait-before-kill 在终止查询之前等待时长

过滤匹配

这些选项用于过滤匹配查询规则,如果查询不匹配,不会将其删除,如果使用ignore选项,其有最高优先级,默认所有的匹配是区分大小写的,可以通过正则表达式不区分大小写(?i-xsm:select)

参数 描述
–busy-time 匹配运行超过此值的查询,此时command=query时生效
–idle-time Sleep超过此值的查询,此时command=sleep时生效
–ignore-command 忽略command的perl正则表达式的查询
–ignore-db 忽略db的perl正则表达式的查询
–ignore-host 忽略host的perl正则表达式的查询
–ignore-info 忽略info的perl正则表达式的查询
–[no]ignore-self 不kill pt-kill自己的连接,默认为yes
–ignore-state 忽略state的perl正则表达式的查询
–ignore-user 忽略user的perl正则表达式的查询
–match-all 匹配所有未ignore的查询,如未指定ignore,则所有的查询都匹配
–match-command 匹配command的perl正则表达式的查询,常用command包括:
Query
Sleep
Binlog Dump
Connect
Delayed insert
Execute
Fetch
Init DB
Kill
Prepare
Processlist
Quit
Reset stmt
Table Dump
–match-db 匹配db的perl正则表达式的查询
–match-host 匹配host的perl正则表达式的查询,也支持host:port模式
–match-info 匹配info的perl正则表达式的查询
–match-state 匹配state的perl正则表达式的查询,常用state包括:
Locked
login
copy to tmp table
Copying to tmp table
Copying to tmp table on disk
Creating tmp table
executing
Reading from net
Sending data
Sorting for order
Sorting result
Table lock
Updating
–match-user 匹配user的perl正则表达式的查询
–replication-threads 允许匹配复制线程
–test-matching 用于测试匹配,此选项禁用了–run-time,–interval和–[no]ignore-self

匹配所有

这些匹配适用于所有查询,通过指定–group-by来创建组,如未指定,会匹配所有组

参数 描述
–any-busy-time 任何查询超过此值则匹配查询,如为10,则至少有一个查询时长超过10,才会匹配
–each-busy-time 每个查询超过些值则匹配查询,如为10,则只有在每个查询时长超过10,才会匹配
–query-count 至少有这么多查询才匹配
–verbose 打印正在完成的工作信息

Action

对所有的匹配查询采取的操作,默认顺序为–print,–execute-command,–kill/–kill-query.它允许–execute-command查看–print和–kill/–kill-query的输出,默认pt-kill不会传递任何信息.

参数 描述
–execute-command 查询匹配时执行此命令
–kill 终止连接并退出
–kill-busy-commands 逗号运行命令列表,如果运行超过–busy-time,会被kill,默认query
例如:–kill-busy-commands=Query,Execute
–kill-query 只杀掉连接执行的语句,线程会被终止
–print 打印匹配查询的KILL语句

DSN选项

这些选项选用于创建DSN,每个选项都给出了option=value,选项区分大小写

参数 描述
A dsn: charset; copy: yes 默认字符集
D dsn: database; copy: yes 默认数据库
F dsn: mysql_read_default_file; copy: yes 仅从指定文件读取参数
h dsn: host; copy: yes 连接的host
p dsn: password; copy: yes 连接的密码
P dsn: port; copy: yes 连接的端口
S dsn: mysql_socket; copy: yes 连接的socket
u dsn: user; copy: yes 连接的用户
t 记录操作的表,通过–log-dsn指定

案例

通过测试用例来展示pt-kill的规则及其优秀的工作能力,构造一个测试环境,使用sysbench来做mysql的压力测试,在测试过程中通过pt-kill来匹配规则并kill

测试匹配

1.查看当前进程列表添加到proclist.txt文件

1
mysql -S /opt/mysql3306/data/mysql3306.sock -e "SHOW PROCESSLIST" > proclist.txt

2.查看proclist.txt文件内容

image

3.通过pt-kill匹配测试查询

1
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --test-matching proclist.txt --match-command="Query"   --victim all --interval 1 --match-state="Sending data" --print

image

可以看出测试匹配出了3个command为Query,state为Sending data的sql语句,并且kill的id与show processlist中的id对应

Host匹配查询

1.通过pt-kill连接host匹配并打印出来

1
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1" --victim all --interval 1 --print

image

仅匹配了host为127.0.0.1,并且未指定其他匹配,从输出上看command为Query,Sleep都打印了出来

DB匹配查询

1.通过pt-kill连接db匹配并打印出来

1
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-db="sysbench" --victim all --interval 1 --print

image

仅匹配了db为sysbench,并且未指定其他匹配,从输出上看command为Query,Sleep都打印了出来

USER匹配查询

1.通过pt-kill连接user匹配并打印出来

1
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-user="root" --victim all --interval 1 --print

image

仅匹配了user为root,并且未指定其他匹配,从输出上看command为Query,Sleep都打印了出来

Command匹配查询

1.通过pt-kill连接command匹配并打印出来,分别打印c
mmand只包括Sleep和包括Sleep及Query的示例

1
2
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-command="Sleep" --victim all --interval 1 --print
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-command="Sleep|Query" --victim all --interval 1 --print

image

当command匹配为Sleep时,只打印了command为Sleep的匹配查询

当command匹配为Sleep和Query时,打印了command为Sleep和Query的匹配查询

多个条件匹配时,用|分割

State匹配查询

1.通过pt-kill连接state匹配并打印出来,分别打印state只包括starting和包括starting及Creating sort index和包括Creating sort index及Sending data的示例

1
2
3
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-state="starting" --victim all --interval 1 --print
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-state="starting|Creating sort index" --victim all --interval 1 --print
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-state="Creating sort index|Sending data" --victim all --interval 1 --print

image

当state匹配为starting时,只打印了state为starting的匹配查询

当state匹配为starting和Creating sort index时,打印了state为starting和Creating sort index的匹配查询

当state匹配为Creating sort index和Sending data时,打印了state为Creating sort index和Sending data的匹配查询

Info匹配查询

1.通过pt-kill连接info匹配并打印出来,分别打印info只包括COMMIT和只包括SELECT和包括COMMIT及SELECT的示例

1
2
3
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-info="COMMIT" --victim all --interval 1 --print
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-info="SELECT|COMMIT" --victim all --interval 1 --print
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-info="SELECT" --victim all --interval 1 --print

image

当info匹配为COMMIT时,只打印了info为starting的匹配查询

当info匹配为SELECT时,只打印了info为SELECT的匹配查询

当info匹配为COMMIT和SELECT时,打印了info为SELECT和COMMIT的匹配查询

组合匹配查询

1.通过pt-kill连接匹配规则如下

  • 匹配host为127.0.0.1
  • 匹配user为root
  • 匹配db为sysbench
  • 匹配command为Query
  • 匹配state为Opening tables
  • 匹配info为SELECT
    1
    pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1" --match-user="root" --match-db="sysbench" --match-command="Query" --match-state="Opening tables" --match-info="SELECT" --victim all --interval 1 --print

image

只有匹配上面所有的条件,才会打印出来匹配的SQL详情

Ignore忽略匹配

1.通过pt-kill连接匹配规则如下

  • 匹配host为127.0.0.1
  • 忽略匹配info为SELECT
    1
    pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1" --ignore-info="SELECT" --victim all --interval 1 --print

image

当指定ignore时,ignore具有最高的优先级,再去匹配其他规则

DSN匹配查询

1.通过pt-kill连接匹配,结合ignore并打印出来,同时记录到DNS表中,规则如下

  • 匹配host为127.0.0.1
  • 匹配user为root
  • 匹配db为sysbench
  • 匹配command为Query
  • 匹配state为Opening tables
  • 匹配info为SELECT
  • 忽略匹配info为update
    1
    pt-kill --no-version-check --log-dsn D=test,t=kill_log --create-log-table --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1" --match-user="root" --match-db="sysbench" --match-command="Query" --match-state="Sending data" --match-info="SELECT" --ignore-info=”update” --victim all --interval 1 --kill-query

image

2.结果分析

只有匹配上面所有的条件,才会将kill的SQL详情保存在test库下的kill_log表中,DSN模式下使用print不会记录到表kill_log中

3.Kill_log表结构

image

4.kill_log表记录

image

建议

方法推荐

  • 推荐使用组合匹配加上ignore模式精确匹配,减小误操作的几率
  • 先测试再执行,推荐用print方法,也可使用–test-matching方法
  • 推荐使用DSN模式,能够记录pt-kill的历史记录,方便分析,追溯
  • 推荐使用–kill-query替代–kill,–kill-query对应用更友好
  • 推荐使用后台进程方式–daemonize结合–run-time使用

语句推荐

1.查询推荐

1
pt-kill --no-version-check --log-dsn D=test,t=kill_log --create-log-table --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1|localhost" --match-user="root" --match-db="sysbench|test" --match-command="Query" --match-state="Sending data|User sleep" --match-info="(?i-xsm:select)" --ignore-info="(?i-smx:^insert|^update|^delete|^load)" --victim all --interval 1 --print

  1. kill语句保存到DSN并输出到log
    1
    pt-kill --no-version-check --log-dsn D=test,t=kill_log --create-log-table --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1|localhost" --match-user="root" --match-db="sysbench|test" --match-command="Query" --match-state="Sending data|User sleep" --match-info="(?i-xsm:select)" --ignore-info="(?i-smx:^insert|^update|^delete|^load)" --victim all --interval 1 --kill-query --daemonize --print --log=/tmp/pt-kill.log

3.kill语句仅输出到log

1
pt-kill --no-version-check --host 127.0.0.1 --port 3306 --user root --password 111111 --socket /opt/mysql3306/data/mysql3306.sock --match-host="127.0.0.1|localhost" --match-user="root" --match-db="sysbench|test" --match-command="Query" --match-state="Sending data|User sleep" --match-info="(?i-xsm:select)" --ignore-info="(?i-smx:^insert|^update|^delete|^load)" --victim all --interval 1  --kill-query --daemonize --print --log=/tmp/pt-kill.log

生产环境

当生产出现并发队列高导致cpu高时,查看如果为查询引起的话,可直接根据时间来kill,但是可能会造成误杀,在处理问题速度为主的情况下,此种方式也可选择

打印select大于5s的查询

1
pt-kill --no-version-check --socket /opt/mysql3306/data/mysql.sock --match-info="(?i-xsm:select)" --busy-time 5s --victim all --interval 1 --print

kill大于5s的查询

1
pt-kill --no-version-check --socket /opt/mysql3306/data/mysql.sock --match-info="(?i-xsm:select)" --busy-time 5s --victim all --interval 1 --print --kill-query --daemonize --log=/tmp/pt-kill.log

MySQL Group Replication初探

发表于 2017-11-28 | 分类于 集群高可用
字数统计: | 阅读时长 ≈

前言

MySQL Group Replication简称MGR,又称MySQL组复制,是MySQL官方于2016年12月12日推出的一个全新的高可用与高扩展的解决方案,首发集成于MySQL5.7.17版本中。MySQL组复制提供了高可用、高扩展、高可靠的MySQL集群服务。高一致性,基于原生复制及paxos协议的组复制技术,并以插件的方式提供,提供一致数据安全保证;高容错性,只要不是大多数节点坏掉就可以继续工作,有自动检测机制,当不同节点产生资源急用冲突时时,不会出现错误,按照先到者优先进行处理,并且内置了自动化脑裂防护机制;高扩展性,节点的新增和移除都是自动的,新节点加入后,会自动从其他节点上同步状态,直到新节点和其他节点保持一致,如果某节点被移除了,其他节点自动更新组信息,自动维护新的组信息;高灵活性,有单主模式和多主模式,单主模式下,会自动选主,所有更新操作都在主上进行;多主模式下,所有server都可以处理更新操作。

要求和限制

基础结构

  • 数据必须存储在InnoDB存储引擎中
  • 表必须定义一个显式主键
  • 通信引擎仅支持IPv4
  • 需要低延迟,高带宽的网络

    server实例配置

  • 必须开启binlog
  • 必须开启log-slave-updates
  • binlog格式必须为ROW格式
  • 必须开启GTID模式
  • 复制相关信息必须使用表存储(–master-info-repository = TABLE/–relay-log-info-repository = TABLE)
  • 事务写集合必须打开(Transaction write set extraction)
  • root用户必须存在(group replication用于检查组用户是否存在)

    使用限制

  • 不支持binlog的checksum,需要设置–binlog-checksum = NONE
  • 不支持savepoint
  • 不支持可串行serializable事务隔离级别
  • 不支持gap locks
  • 不支持lock tables,unlock tables
  • 多主模式下,不支持多节点同时对一个表进行DDL vs DDL/DML
  • 多主模式下,不支持多级关联外键

工作原理

MySQL组复制是一个MySQL插件,它建立在现有的MySQL复制基础结构上,利用了二进制日志,基于行的日志记录和全局事务标识符等功能。它集成了当前的MySQL框架,如性能模式、插件和服务基础设施等。
组复制(Group Replication)基于分布式一致性算法(Paxos协议的变体)实现,一个组允许部分节点挂掉,只要保证绝大多数节点仍然存活并且之间的通讯是没有问题的,那么这个组对外仍然能够提供服务,它是一种被使用在容错系统中的技术。Group Replication(复制组)是由能够相互通信的多个服务器(节点)组成的。在通信层,Group replication实现了一系列的机制:比如原子消息(atomic message delivery)和全序化消息(total ordering of messages)。这些原子化,抽象化的机制,为实现更先进的数据库复制方案提供了强有力的支持。MySQL Group Replication正是基于这些技术和概念,实现了一种多主全更新的复制协议。简而言之,一个Group Replication就是一组节点,每个节点都可以独立执行事务,而读写事务则会在于group内的其他节点进行协调之后再commit。因此,当一个事务准备提交时,会自动在group内进行原子性的广播,告知其他节点变更了什么内容/执行了什么事务。这种原子广播的方式,使得这个事务在每一个节点上都保持着同样顺序。这意味着每一个节点都以同样的顺序,接收到了同样的事务日志,所以每一个节点以同样的顺序重演了这些事务日志,最终整个group保持了完全一致的状态。然而,不同的节点上执行的事务之间有可能存在资源争用。这种现象容易出现在两个不同的并发事务上。假设在不同的节点上有两个并发事务,更新了同一行数据,那么就会发生资源争用。面对这种情况,Group Replication判定先提交的事务为有效事务,会在整个group里面重放,后提交的事务会直接中断,或者回滚,最后丢弃掉。因此,这也是一个无共享的复制方案,每一个节点都保存了完整的数据副本。

组织结构

image

  • API层 负责完成和MySQL Server的交互,获取server的状态,截获事务提交,干涉事务提交或者回滚。
  • 组件层 特定功能的组件,Capture负责收集事务执行相关信息,Applier负责应用集群事务到本地,Recovery负责新节点的数据恢复。
  • 复制层 负责冲突验证,接收和应用集群事务。
  • 集群通信层 基于Paxos协议的集群通信引擎以及和上层组件的交互接口。

容错

MySQL组复制构建在paxos分布式算法实现的基础上,以提供不同server之间的分布式协调。因此,它需要大多数server处于活动状态以达到仲裁成员数,从而做出决定。这对系统可以容忍的不影响其自身及其整体功能的故障数量有直接影响。容忍f个故障所需的server数量(n)为n=2*f+1。
在实践中,这意味着为了容忍一个故障,组必须有三个server。因此,如果一个服务器故障,仍然有两个服务器形成大多数(三分之二)来允许系统自动地继续运行。但是,如果第二个server意外地fail掉,则该组(剩下一个server)锁定,因为没有多数可以达成协议。

列表

组大小 多数 允许的即使故障数
1 1 0
2 2 0
3 2 1
4 3 1
5 3 2
6 4 2
7 4 3
8 5 3
9 5 4

实践

在单主模式下部署组复制

准备

IP:192.168.7.50
system:Centos 6.5
hostname:jiessie
disk:300G ssd
memory:8G
cpu:4core
节点数:3

二进制安装包下载

二进制包安装目录:/hwdata/data/mysql5.7.20/base
下载地址:https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.20-linux-glibc2.12-x86_64.tar.gz
目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
/hwdata/data/mysql5.7.20/base
[root@jiessie base]# ll
total 52
-rw-r--r-- 1 7161 31415 17987 Sep 13 23:48 COPYING
-rw-r--r-- 1 7161 31415 2478 Sep 13 23:48 README
drwxr-xr-x 2 root root 4096 Nov 29 11:22 bin
drwxr-xr-x 2 root root 4096 Nov 29 11:22 docs
drwxr-xr-x 3 root root 4096 Nov 29 11:22 include
drwxr-xr-x 5 root root 4096 Nov 29 11:22 lib
drwxr-xr-x 4 root root 4096 Nov 29 11:22 man
drwxr-xr-x 28 root root 4096 Nov 29 11:22 share
drwxr-xr-x 2 root root 4096 Nov 29 11:22 support-files
[root@jiessie base]#

数据目录

数据目录:/hwdata/data/mysql5.7.20/data
三节点的数据,分别存放于data子目录下的s1,s2,s3目录中

1
2
3
4
5
6
7
8
9
[root@jiessie mysql5.7.20]# pwd
/hwdata/data/mysql5.7.20
[root@jiessie mysql5.7.20]# mkdir data/{s1,s2,s3}
[root@jiessie mysql5.7.20]# ll data/
total 12
drwxr-xr-x 2 root root 4096 Nov 29 17:23 s1
drwxr-xr-x 2 root root 4096 Nov 29 17:23 s2
drwxr-xr-x 2 root root 4096 Nov 29 17:23 s3
[root@jiessie mysql5.7.20]#

配置文件

配置文件:/hwdata/data/mysql5.7.20/conf
三节点的配置文件,分别是conf目录下的s1.cnf,s2.cnf,s3.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
[root@jiessie mysql5.7.20]# ll conf/
total 12
-rw-r--r-- 1 root root 948 Nov 29 17:35 s1.cnf
-rw-r--r-- 1 root root 948 Nov 29 17:36 s2.cnf
-rw-r--r-- 1 root root 948 Nov 29 17:37 s3.cnf
[root@jiessie mysql5.7.20]# cat conf/s1.cnf
[mysqld]
datadir=/hwdata/data/mysql5.7.20/data/s1
basedir=/hwdata/data/mysql5.7.20/base
port=9001
socket=/hwdata/data/mysql5.7.20/data/s1/s1.sock

server_id=1
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
innodb_buffer_pool_instances=4
innodb_buffer_pool_size=128M
innodb_flush_log_at_trx_commit=2
sync_binlog=0
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-workers=4
slave_preserve_commit_order=on
#group replication
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address= "127.0.0.1:10011"
loose-group_replication_group_seeds= "127.0.0.1:10011,127.0.0.1:10012,127.0.0.1:10013"
loose-group_replication_bootstrap_group= off
loose-group_replication_single_primary_mode=true
loose-group_replication_ip_whitelist="127.0.0.1/20,192.168.7.50/20"
[root@jiessie mysql5.7.20]#
[root@jiessie mysql5.7.20]# cat conf/s2.cnf
[mysqld]
datadir=/hwdata/data/mysql5.7.20/data/s2
basedir=/hwdata/data/mysql5.7.20/base
port=9002
socket=/hwdata/data/mysql5.7.20/data/s2/s2.sock

server_id=2
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
innodb_buffer_pool_instances=4
innodb_buffer_pool_size=128M
innodb_flush_log_at_trx_commit=2
sync_binlog=0
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-workers=4
slave_preserve_commit_order=on
#group replication
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address= "127.0.0.1:10012"
loose-group_replication_group_seeds= "127.0.0.1:10011,127.0.0.1:10012,127.0.0.1:10013"
loose-group_replication_bootstrap_group= off
loose-group_replication_single_primary_mode=true
loose-group_replication_ip_whitelist="127.0.0.1/20,192.168.7.50/20"
[root@jiessie mysql5.7.20]#
[root@jiessie mysql5.7.20]# cat conf/s3.cnf
[mysqld]
datadir=/hwdata/data/mysql5.7.20/data/s3
basedir=/hwdata/data/mysql5.7.20/base
port=9003
socket=/hwdata/data/mysql5.7.20/data/s3/s3.sock

server_id=3
gtid_mode=ON
enforce_gtid_consistency=ON
master_info_repository=TABLE
relay_log_info_repository=TABLE
binlog_checksum=NONE
log_slave_updates=ON
log_bin=binlog
binlog_format=ROW
innodb_buffer_pool_instances=4
innodb_buffer_pool_size=128M
innodb_flush_log_at_trx_commit=2
sync_binlog=0
slave-parallel-type=LOGICAL_CLOCK
slave-parallel-workers=4
slave_preserve_commit_order=on
#group replication
transaction_write_set_extraction=XXHASH64
loose-group_replication_group_name="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
loose-group_replication_start_on_boot=off
loose-group_replication_local_address= "127.0.0.1:10013"
loose-group_replication_group_seeds= "127.0.0.1:10011,127.0.0.1:10012,127.0.0.1:10013"
loose-group_replication_bootstrap_group= off
loose-group_replication_single_primary_mode=true
loose-group_replication_ip_whitelist="127.0.0.1/20,192.168.7.50/20"
[root@jiessie mysql5.7.20]#

  1. 三节点中配置不一致的参数有以下:
  • datadir #数据目录
  • port #服务端口
  • socket #socket位置
  • server_id #服务器标识
  • group_replication_local_address #组复制的本地地址
  1. 组复制相关参数:
  • transaction_write_set_extraction #指示Server必须为每个事务收集写集合,并使用指定算法将其编码为散列
  • loose-group_replication_group_name #组复制名称
  • loose-group_replication_start_on_boot #server启动时是否自动启动组复制
  • loose-group_replication_local_address #绑定的ip和端口接受其他组成员的连接
  • loose-group_replication_group_seeds #本行为告诉服务器当服务器加入组时,应当连接到此列表的这些种子服务器进行配置。本设置可以不是全部的组成员地址
  • loose-group_replication_bootstrap_group #配置是否自动启动引导组
  • loose-group_replication_single_primary_mode #配置单主还是多主模式
  • loose-group_replication_ip_whitelist #默认情况下只允许127.0.0.1连接到复制组,如果是其他IP则需要配置
  1. 开启并行复制
  • set global slave_parallel_type = ‘LOGICAL_CLOCK’;
  • set global slave_parallel_workers = N;
  • set global slave_preserve_commit_order = ON;
    这三个参数可以开启并行复制,实践中没有配置,特此提示。

    初始化

    使用mysqld分别对s1,s2,s3进行初始化:
  • base/bin/mysqld –initialize-insecure –basedir=/hwdata/data/mysql5.7.20/base –datadir=/hwdata/data/mysql5.7.20/data/s1
  • base/bin/mysqld –initialize-insecure –basedir=/hwdata/data/mysql5.7.20/base –datadir=/hwdata/data/mysql5.7.20/data/s2
  • base/bin/mysqld –initialize-insecure –basedir=/hwdata/data/mysql5.7.20/base –datadir=/hwdata/data/mysql5.7.20/data/s3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    [root@jiessie mysql5.7.20]# base/bin/mysqld --initialize-insecure --basedir=/hwdata/data/mysql5.7.20/base --datadir=/hwdata/data/mysql5.7.20/data/s1                   
    2017-11-30T04:55:56.436002Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
    2017-11-30T04:55:57.095836Z 0 [Warning] InnoDB: New log files created, LSN=45790
    2017-11-30T04:55:57.216615Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
    2017-11-30T04:55:57.284024Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: c0acc2c7-d58a-11e7-b59f-00163e00dc49.
    2017-11-30T04:55:57.287290Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
    2017-11-30T04:55:57.287678Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
    [root@jiessie mysql5.7.20]#
    [root@jiessie mysql5.7.20]# base/bin/mysqld --initialize-insecure --basedir=/hwdata/data/mysql5.7.20/base --datadir=/hwdata/data/mysql5.7.20/data/s2
    2017-11-30T04:56:20.520937Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
    2017-11-30T04:56:21.152584Z 0 [Warning] InnoDB: New log files created, LSN=45790
    2017-11-30T04:56:21.278863Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
    2017-11-30T04:56:21.349754Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: cf04e66c-d58a-11e7-b97e-00163e00dc49.
    2017-11-30T04:56:21.352655Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
    2017-11-30T04:56:21.353069Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
    [root@jiessie mysql5.7.20]#
    [root@jiessie mysql5.7.20]# base/bin/mysqld --initialize-insecure --basedir=/hwdata/data/mysql5.7.20/base --datadir=/hwdata/data/mysql5.7.20/data/s3
    2017-11-30T04:56:28.986804Z 0 [Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).
    2017-11-30T04:56:29.712932Z 0 [Warning] InnoDB: New log files created, LSN=45790
    2017-11-30T04:56:29.902130Z 0 [Warning] InnoDB: Creating foreign key constraint system tables.
    2017-11-30T04:56:29.970877Z 0 [Warning] No existing UUID has been found, so we assume that this is the first time that this server has been started. Generating a new UUID: d4286108-d58a-11e7-807d-00163e00dc49.
    2017-11-30T04:56:29.973699Z 0 [Warning] Gtid table is not ready to be used. Table 'mysql.gtid_executed' cannot be opened.
    2017-11-30T04:56:29.974087Z 1 [Warning] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
    [root@jiessie mysql5.7.20]# tree -L 2 data/
    data/
    |-- s1
    | |-- auto.cnf
    | |-- ib_buffer_pool
    | |-- ib_logfile0
    | |-- ib_logfile1
    | |-- ibdata1
    | |-- mysql
    | |-- performance_schema
    | `-- sys
    |-- s2
    | |-- auto.cnf
    | |-- ib_buffer_pool
    | |-- ib_logfile0
    | |-- ib_logfile1
    | |-- ibdata1
    | |-- mysql
    | |-- performance_schema
    | `-- sys
    `-- s3
    |-- auto.cnf
    |-- ib_buffer_pool
    |-- ib_logfile0
    |-- ib_logfile1
    |-- ibdata1
    |-- mysql
    |-- performance_schema
    `-- sys

    12 directories, 15 files
    [root@jiessie mysql5.7.20]#

启动数据库

添加权限:chown -R mysql:mysql /hwdata/data/mysql5.7.20/data/
分别启动三个节点数据库:

  • base/bin/mysqld_safe –defaults-file=/hwdata/data/mysql5.7.20/conf/s1.cnf &
  • base/bin/mysqld_safe –defaults-file=/hwdata/data/mysql5.7.20/conf/s2.cnf &
  • base/bin/mysqld_safe –defaults-file=/hwdata/data/mysql5.7.20/conf/s3.cnf &
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    [root@jiessie mysql5.7.20]# base/bin/mysqld_safe --defaults-file=/hwdata/data/mysql5.7.20/conf/s1.cnf &
    [1] 25168
    [root@jiessie mysql5.7.20]# Logging to '/hwdata/data/mysql5.7.20/data/s1/jiessie.err'.
    2017-11-30T05:06:34.402042Z mysqld_safe Starting mysqld daemon with databases from /hwdata/data/mysql5.7.20/data/s1

    [root@jiessie mysql5.7.20]# base/bin/mysqld_safe --defaults-file=/hwdata/data/mysql5.7.20/conf/s2.cnf &
    [2] 25681
    [root@jiessie mysql5.7.20]# Logging to '/hwdata/data/mysql5.7.20/data/s2/jiessie.err'.
    2017-11-30T05:06:48.204177Z mysqld_safe Starting mysqld daemon with databases from /hwdata/data/mysql5.7.20/data/s2

    [root@jiessie mysql5.7.20]# base/bin/mysqld_safe --defaults-file=/hwdata/data/mysql5.7.20/conf/s3.cnf &
    [3] 26193
    [root@jiessie mysql5.7.20]# Logging to '/hwdata/data/mysql5.7.20/data/s3/jiessie.err'.
    2017-11-30T05:06:59.779426Z mysqld_safe Starting mysqld daemon with databases from /hwdata/data/mysql5.7.20/data/s3

    [root@jiessie mysql5.7.20]#

查看进程:

1
2
3
4
5
[root@jiessie mysql5.7.20]# netstat -tunlp|grep mysql 
tcp 0 0 0.0.0.0:9001 0.0.0.0:* LISTEN 25609/mysqld
tcp 0 0 0.0.0.0:9002 0.0.0.0:* LISTEN 26122/mysqld
tcp 0 0 0.0.0.0:9003 0.0.0.0:* LISTEN 26634/mysqld
[root@jiessie mysql5.7.20]#

添加复制用户

创建具有replication slave权限的MySQL用户,此操作不应记录到二进制中,以避免将更改传递到其他slave实例.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SET SQL_LOG_BIN=0;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE USER rpl_user@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO rpl_user@'%' IDENTIFIED BY 'rpl_pass';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql> SET SQL_LOG_BIN=1;
Query OK, 0 rows affected (0.00 sec)

mysql> CHANGE MASTER TO MASTER_USER='rpl_user', MASTER_PASSWORD='rpl_pass' FOR CHANNEL 'group_replication_recovery';
Query OK, 0 rows affected, 2 warnings (0.04 sec)

mysql> exit
Bye
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SET SQL_LOG_BIN=0;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE USER rpl_user@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO rpl_user@'%' IDENTIFIED BY 'rpl_pass';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql> SET SQL_LOG_BIN=1;
Query OK, 0 rows affected (0.00 sec)

mysql> CHANGE MASTER TO MASTER_USER='rpl_user', MASTER_PASSWORD='rpl_pass' FOR CHANNEL 'group_replication_recovery';
Query OK, 0 rows affected, 2 warnings (0.04 sec)

mysql> exit
Bye
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SET SQL_LOG_BIN=0;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE USER rpl_user@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT REPLICATION SLAVE ON *.* TO rpl_user@'%' IDENTIFIED BY 'rpl_pass';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql> SET SQL_LOG_BIN=1;
Query OK, 0 rows affected (0.00 sec)

mysql> CHANGE MASTER TO MASTER_USER='rpl_user', MASTER_PASSWORD='rpl_pass' FOR CHANNEL 'group_replication_recovery';
Query OK, 0 rows affected, 2 warnings (0.03 sec)

mysql> exit
Bye
[root@jiessie mysql5.7.20]#

启动组复制

配置并启动s1后,安装组复制插件,然后连接到server并执行以下命令.

注意:
创建一个Group Replication,需要在一个节点初始化,也只需要在一个节点初始化,不可在多个节点都执行.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> INSTALL PLUGIN group_replication SONAME 'group_replication.so';
Query OK, 0 rows affected (0.00 sec)

mysql> show plugins;
+----------------------------+----------+--------------------+----------------------+---------+
| Name | Status | Type | Library | License |
+----------------------------+----------+--------------------+----------------------+---------+
| binlog | ACTIVE | STORAGE ENGINE | NULL | GPL |
| mysql_native_password | ACTIVE | AUTHENTICATION | NULL | GPL |
| sha256_password | ACTIVE | AUTHENTICATION | NULL | GPL |
| PERFORMANCE_SCHEMA | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MRG_MYISAM | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MEMORY | ACTIVE | STORAGE ENGINE | NULL | GPL |
| InnoDB | ACTIVE | STORAGE ENGINE | NULL | GPL |
| INNODB_TRX | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_LOCKS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_LOCK_WAITS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_CMP | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_CMP_RESET | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_CMPMEM | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_CMPMEM_RESET | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_CMP_PER_INDEX | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_CMP_PER_INDEX_RESET | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_BUFFER_PAGE | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_BUFFER_PAGE_LRU | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_BUFFER_POOL_STATS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_TEMP_TABLE_INFO | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_METRICS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_FT_DEFAULT_STOPWORD | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_FT_DELETED | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_FT_BEING_DELETED | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_FT_CONFIG | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_FT_INDEX_CACHE | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_FT_INDEX_TABLE | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_TABLES | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_TABLESTATS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_INDEXES | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_COLUMNS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_FIELDS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_FOREIGN | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_FOREIGN_COLS | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_TABLESPACES | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_DATAFILES | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| INNODB_SYS_VIRTUAL | ACTIVE | INFORMATION SCHEMA | NULL | GPL |
| CSV | ACTIVE | STORAGE ENGINE | NULL | GPL |
| MyISAM | ACTIVE | STORAGE ENGINE | NULL | GPL |
| ARCHIVE | ACTIVE | STORAGE ENGINE | NULL | GPL |
| partition | ACTIVE | STORAGE ENGINE | NULL | GPL |
| BLACKHOLE | ACTIVE | STORAGE ENGINE | NULL | GPL |
| FEDERATED | DISABLED | STORAGE ENGINE | NULL | GPL |
| ngram | ACTIVE | FTPARSER | NULL | GPL |
| group_replication | ACTIVE | GROUP REPLICATION | group_replication.so | GPL |
+----------------------------+----------+--------------------+----------------------+---------+
45 rows in set (0.00 sec)

mysql> SET GLOBAL group_replication_bootstrap_group=ON;
Query OK, 0 rows affected (0.00 sec)

mysql> START GROUP_REPLICATION;
Query OK, 0 rows affected (3.02 sec)

mysql> SET GLOBAL group_replication_bootstrap_group=OFF;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | jiessie | 9001 | ONLINE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
1 row in set (0.00 sec)

mysql> exit
Bye
[root@jiessie mysql5.7.20]#

添加其他组复制节点

在其他2个节点上开启组复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> INSTALL PLUGIN group_replication SONAME 'group_replication.so';
Query OK, 0 rows affected (0.00 sec)

mysql> START GROUP_REPLICATION;
Query OK, 0 rows affected (6.57 sec)

mysql> exit
Bye
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> INSTALL PLUGIN group_replication SONAME 'group_replication.so';
Query OK, 0 rows affected (0.00 sec)

mysql> START GROUP_REPLICATION;
Query OK, 0 rows affected (3.27 sec)

mysql> exit
Bye
[root@jiessie mysql5.7.20]#

测试数据同步

在s1节点上插入数据,在其他节点上查看数据是否同步及binlog详情

  1. 刷新flush logs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "flush logs;show binary logs;"                                  +---------------+-----------+
    | Log_name | File_size |
    +---------------+-----------+
    | binlog.000001 | 1101 |
    | binlog.000002 | 1162 |
    | binlog.000003 | 2329 |
    | binlog.000004 | 230 |
    | binlog.000005 | 1017 |
    | binlog.000006 | 190 |
    +---------------+-----------+
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock -e "flush logs;show binary logs;"
    +---------------+-----------+
    | Log_name | File_size |
    +---------------+-----------+
    | binlog.000001 | 169 |
    | binlog.000002 | 2054 |
    | binlog.000003 | 3106 |
    | binlog.000004 | 190 |
    +---------------+-----------+
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock -e "flush logs;show binary logs;"
    +---------------+-----------+
    | Log_name | File_size |
    +---------------+-----------+
    | binlog.000001 | 169 |
    | binlog.000002 | 150 |
    | binlog.000003 | 4970 |
    | binlog.000004 | 190 |
    +---------------+-----------+
    [root@jiessie mysql5.7.20]#
  2. 节点1插入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "create database if not exists test;create table test.t1(id int unsigned not null auto_increment primary key,name varchar(20))"           
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "desc test.t1;select * from test.t1"
    +-------+------------------+------+-----+---------+----------------+
    | Field | Type | Null | Key | Default | Extra |
    +-------+------------------+------+-----+---------+----------------+
    | id | int(10) unsigned | NO | PRI | NULL | auto_increment |
    | name | varchar(20) | YES | | NULL | |
    +-------+------------------+------+-----+---------+----------------+
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "insert into test.t1 values(null,'111')"
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "select * from test.t1"
    +----+------+
    | id | name |
    +----+------+
    | 1 | 111 |
    +----+------+
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "show binlog events in 'binlog.000006'"
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    | Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    | binlog.000006 | 4 | Format_desc | 1 | 123 | Server ver: 5.7.20-log, Binlog ver: 4 |
    | binlog.000006 | 123 | Previous_gtids | 1 | 190 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-19 |
    | binlog.000006 | 190 | Gtid | 1 | 251 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:20' |
    | binlog.000006 | 251 | Query | 1 | 360 | create database if not exists test |
    | binlog.000006 | 360 | Gtid | 1 | 421 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:21' |
    | binlog.000006 | 421 | Query | 1 | 582 | create table test.t1(id int unsigned not null auto_increment primary key,name varchar(20)) |
    | binlog.000006 | 582 | Gtid | 1 | 643 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:22' |
    | binlog.000006 | 643 | Query | 1 | 712 | BEGIN |
    | binlog.000006 | 712 | Table_map | 1 | 756 | table_id: 223 (test.t1) |
    | binlog.000006 | 756 | Write_rows | 1 | 796 | table_id: 223 flags: STMT_END_F |
    | binlog.000006 | 796 | Xid | 1 | 823 | COMMIT /* xid=151 */ |
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    [root@jiessie mysql5.7.20]#
  3. 其他节点查看数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock -e "show binlog events in 'binlog.000004'"   
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    | Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    | binlog.000004 | 4 | Format_desc | 2 | 123 | Server ver: 5.7.20-log, Binlog ver: 4 |
    | binlog.000004 | 123 | Previous_gtids | 2 | 190 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-19 |
    | binlog.000004 | 190 | Gtid | 1 | 251 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:20' |
    | binlog.000004 | 251 | Query | 1 | 360 | create database if not exists test |
    | binlog.000004 | 360 | Gtid | 1 | 421 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:21' |
    | binlog.000004 | 421 | Query | 1 | 582 | create table test.t1(id int unsigned not null auto_increment primary key,name varchar(20)) |
    | binlog.000004 | 582 | Gtid | 1 | 643 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:22' |
    | binlog.000004 | 643 | Query | 1 | 707 | BEGIN |
    | binlog.000004 | 707 | Table_map | 1 | 751 | table_id: 221 (test.t1) |
    | binlog.000004 | 751 | Write_rows | 1 | 791 | table_id: 221 flags: STMT_END_F |
    | binlog.000004 | 791 | Xid | 1 | 818 | COMMIT /* xid=71 */ |
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock -e "show binlog events in 'binlog.000004'"
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    | Log_name | Pos | Event_type | Server_id | End_log_pos | Info |
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    | binlog.000004 | 4 | Format_desc | 3 | 123 | Server ver: 5.7.20-log, Binlog ver: 4 |
    | binlog.000004 | 123 | Previous_gtids | 3 | 190 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-19 |
    | binlog.000004 | 190 | Gtid | 1 | 251 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:20' |
    | binlog.000004 | 251 | Query | 1 | 360 | create database if not exists test |
    | binlog.000004 | 360 | Gtid | 1 | 421 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:21' |
    | binlog.000004 | 421 | Query | 1 | 582 | create table test.t1(id int unsigned not null auto_increment primary key,name varchar(20)) |
    | binlog.000004 | 582 | Gtid | 1 | 643 | SET @@SESSION.GTID_NEXT= 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:22' |
    | binlog.000004 | 643 | Query | 1 | 707 | BEGIN |
    | binlog.000004 | 707 | Table_map | 1 | 751 | table_id: 221 (test.t1) |
    | binlog.000004 | 751 | Write_rows | 1 | 791 | table_id: 221 flags: STMT_END_F |
    | binlog.000004 | 791 | Xid | 1 | 818 | COMMIT /* xid=58 */ |
    +---------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------+
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock -e "select * from test.t1"
    +----+------+
    | id | name |
    +----+------+
    | 1 | 111 |
    +----+------+
    You have new mail in /var/spool/mail/root
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock -e "select * from test.t1"
    +----+------+
    | id | name |
    +----+------+
    | 1 | 111 |
    +----+------+
    [root@jiessie mysql5.7.20]#

节点状态查询

注意:
主机名和/etc/hosts中的信息要保持一致,否则可能会报“There was an error when connecting to the donor server. Please check that group_replication_r
ecovery channel credentials and all MEMBER_HOST column values of performance_schema.replication_group_members table are correct and DNS resolvable.”

1
2
3
4
5
6
7
8
9
10
11
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | jiessie | 9001 | ONLINE |
| group_replication_applier | cf04e66c-d58a-11e7-b97e-00163e00dc49 | jiessie | 9002 | ONLINE |
| group_replication_applier | d4286108-d58a-11e7-807d-00163e00dc49 | jiessie | 9003 | ONLINE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
3 rows in set (0.00 sec)

mysql>

监控组复制

假设MySQL已经在启用了性能模式的情况下编译,使用performance_schema表监控组复制

replication_group_member_stats

复制组中的每个成员都会验证并应用该组提交的事务,有关验证和应用程序的统计信息对于了解申请队列增长情况,触发了多少冲突,检查了多少事务,哪些事务已被所有成员提交等非常有用,performance_shcema.replication_group_member_stats提供与认证过程相关的以下信息

1
2
3
4
5
6
7
mysql> select * from performance_schema.replication_group_member_stats;
+---------------------------+---------------------+--------------------------------------+-----------------------------+----------------------------+--------------------------+------------------------------------+-------------------------------------------+-----------------------------------------+
| CHANNEL_NAME | VIEW_ID | MEMBER_ID | COUNT_TRANSACTIONS_IN_QUEUE | COUNT_TRANSACTIONS_CHECKED | COUNT_CONFLICTS_DETECTED | COUNT_TRANSACTIONS_ROWS_VALIDATING | TRANSACTIONS_COMMITTED_ALL_MEMBERS | LAST_CONFLICT_FREE_TRANSACTION |
+---------------------------+---------------------+--------------------------------------+-----------------------------+----------------------------+--------------------------+------------------------------------+-------------------------------------------+-----------------------------------------+
| group_replication_applier | 15120910223421577:3 | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | 0 | 13 | 0 | 0 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-22 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:22 |
+---------------------------+---------------------+--------------------------------------+-----------------------------+----------------------------+--------------------------+------------------------------------+-------------------------------------------+-----------------------------------------+
1 row in set (0.00 sec)

参数解释
Field 描述
CHANNEL_NAME 组复制通道的名称
VIEW_ID 组复制当前视图标识符
MEMBER_ID 当前连接到server成员的UUID,组中的每个成员具有不同的值
COUNT_TRANSACTIONS_IN_QUEUE 队列中等待冲突检测检查的事务数,冲突检查通过后,他们排队等待应用
COUNT_TRANSACTIONS_CHECKED 已进行过冲突检查的事务数
COUNT_CONFLICTS_DETECTED 未通过冲突检查的事务数
COUNT_TRANSACTIONS_ROWS_VALIDATING 可用于认证的事务行的数量,但没有被垃圾收集
TRANSACTIONS_COMMITTED_ALL_MEMBERS 当前视图的所有成员成功提交的事务,此值为固定的时间间隔更新
LAST_CONFLICT_FREE_TRANSACTION 最后一个经检查无冲突的事务标识符

replication_group_members

用于监控在当前视图中的不同server实例的状态

1
2
3
4
5
6
7
8
9
mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | jiessie | 9001 | ONLINE |
| group_replication_applier | cf04e66c-d58a-11e7-b97e-00163e00dc49 | jiessie | 9002 | ONLINE |
| group_replication_applier | d4286108-d58a-11e7-807d-00163e00dc49 | jiessie | 9003 | ONLINE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
3 rows in set (0.00 sec)

参数解释
Field 描述
CHANNEL_NAME 组复制通道的名称
MEMBER_ID server成员的UUID
MEMBER_HOST 组成员的网络地址
MEMBER_PORT 侦听此成员的MySQL连接端口
MEMBER_STATE 组成员的状态

replication_connection_status

此表显示处理从服务器连接主服务器的IO线程的当前状态

1
2
3
4
5
6
7
mysql> select * from performance_schema.replication_connection_status;
+---------------------------+--------------------------------------+--------------------------------------+-----------+---------------+---------------------------+--------------------------+-------------------------------------------+-------------------+--------------------+----------------------+
| CHANNEL_NAME | GROUP_NAME | SOURCE_UUID | THREAD_ID | SERVICE_STATE | COUNT_RECEIVED_HEARTBEATS | LAST_HEARTBEAT_TIMESTAMP | RECEIVED_TRANSACTION_SET | LAST_ERROR_NUMBER | LAST_ERROR_MESSAGE | LAST_ERROR_TIMESTAMP |
+---------------------------+--------------------------------------+--------------------------------------+-----------+---------------+---------------------------+--------------------------+-------------------------------------------+-------------------+--------------------+----------------------+
| group_replication_applier | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa | NULL | ON | 0 | 0000-00-00 00:00:00 | aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-22 | 0 | | 0000-00-00 00:00:00 |
+---------------------------+--------------------------------------+--------------------------------------+-----------+---------------+---------------------------+--------------------------+-------------------------------------------+-------------------+--------------------+----------------------+
1 row in set (0.00 sec)

参数解释
Field 描述
CHANNEL_NAME 组复制通道的名称
GROUP_NAME 如果此服务器是组的成员,则显示服务器所属的组的名称
SOURCE_UUID 组的标识符,类似组名称,被用作组复制期间生成的所有事务的UUID
THREAD_ID IO线程ID
SERVICE_STATE IO状态,ON(线程存在并处于活动状态或空闲状态),OFF(线程不再存在)或CONNECTING(线程存在并连接到主控制器)
COUNT_RECEIVED_HEARTBEATS 从上次重新启动或复位复制从服务器接收到的心跳信号的总数或发出CHANGE MASTER TO语句
LAST_HEARTBEAT_TIMESTAMP YYMMDD HH:MM:SS格式中 的时间戳,显示复制从站接收到最新的心跳信号的时间
RECEIVED_TRANSACTION_SET 此GTID集合中的事务已由该组的此成员接收
LAST_ERROR_NUMBER 导致IO线程停止的错误号
LAST_ERROR_MESSAGE 导致IO线程停止的错误消息
LAST_ERROR_TIMESTAMP YYMMDD HH:MM:SS格式 的时间戳,IO发生错误的时间
与show slave status的对应关系
replication_connection_status Column SHOW SLAVE STATUS Column
SOURCE_UUID Master_UUID
THREAD_ID None
SERVICE_STATE Slave_IO_Running
RECEIVED_TRANSACTION_SET Retrieved_Gtid_Set
LAST_ERROR_NUMBER Last_IO_Errno
LAST_ERROR_MESSAGE Last_IO_Error
LAST_ERROR_TIMESTAMP Last_IO_Error_Timestamp

replication_applier_status

此表显示从服务器上当前的一般事务执行状态

1
2
3
4
5
6
7
mysql> select * from performance_schema.replication_applier_status;
+---------------------------+---------------+-----------------+----------------------------+
| CHANNEL_NAME | SERVICE_STATE | REMAINING_DELAY | COUNT_TRANSACTIONS_RETRIES |
+---------------------------+---------------+-----------------+----------------------------+
| group_replication_applier | ON | NULL | 0 |
+---------------------------+---------------+-----------------+----------------------------+
1 row in set (0.00 sec)

参数解释
Field 描述
CHANNEL_NAME 组复制通道的名称
SERVICE_STATE 显示复制通道状态是ON还是OFF
REMAINING_DELAY 如果从站在DESIRED_DELAY主站应用事件之后等待 秒数,则此字段包含剩余的延迟秒数。
COUNT_TRANSACTIONS_RETRIES 显示由于从属SQL线程未能应用事务而发生的重试次数。
与show slave status的对应关系
replication_connection_status Column SHOW SLAVE STATUS Column
SERVICE_STATE None
REMAINING_DELAY SQL_Remaining_Delay

组复制中的server状态

Field 描述 Group Synchronized
ONLINE 该成员可以作为一个具有所有功能的组成员,这意味着客户端可以连接并开始执行事务 Yes
RECOVERING 该成员正在成为该组的有效成员,并且正处于恢复过程中,从数据源节点接收状态信息 No
OFFLINE 插件已加载,但成员不属于任何组 No
ERROR 本地成员的状态,只要恢复阶段或应用更改时出现错误,server就会进入此状态 No
UNREACHABLE 每当本地故障检测器怀疑某个给定的server可能由于已经崩溃或被意外地断开而不可访问时,server的状态显示为”UNREACHABLE” No

threads

group replication线程的状态信息可以通过performance_schema.threads表进行查询,目前可以查询到group replication的线程如下:

  • thread/group_rpl/THD_applier_module_receiver
  • thread/group_rpl/THD_certifier_broadcast
  • thread/group_rpl/THD_recovery

    新成员加入组

    配置group_replication_recovery通道

    当新成员加入一个group replication组后,首先要从其他节点上把它加入之前的数据复制过来。这些以前的数据不能过group replication的通信协议进行复制,而是使用了异步复制的机制。group replication需要使用一个名叫group_replication_recovery的异步复制通道(Channel)。用户必须要提前配置这个通道,不过只需要为这个通道配置连接需要的用户名和密码,其他参数在group replication插件启动group_replication_recovery通道时会自动进行配置。
  • change master to master_host=’rpl_user’,master_password=’rpl_pass’ fro channel = ‘group_replication_recovery’;
  • 连接到哪个成员上去复制数据,是由group replication插件随机选择的,因此为group_replication_recovery配置的用户要在每一个成员上存在
    在启动group_replication_recovery通道之前,group replication会自动为其配置master_host和master_port。当一个成员加入组时,会收到组内其他成员的配置信息。配置信息中包括主机名和MySQL服务端口号。主机名和服务端口号是从全局只读变量hostname和port中获取的。如果hostname无法正常解析成ip地址或网络中使用了网络地址映射,group_replication_recovery通道就无法正常工作。有如下两种办法来解决这个问题:
  • 在/etc/hosts中配置所有成员的主机名和ip地址的对应关系
  • 配置MySQL的report_host和report_port的命令行参数。如果用户配置了report_host或report_port,那么group replication会优先使用这个变量中的地址和端口

    成员加入组的步骤

    MySQL的相关配置和创建复制用户的步骤略,group replication加入组的步骤如下:
  • INSTALL PLUGIN group_replication SONAME ‘group_replication.so’;
  • SET GLOBAL group_replication_group_name = “aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa”;
  • SET GLOBAL group_replication_local_address= “127.0.0.1:10014”
  • SET GLOBAL group_replication_group_seeds= “127.0.0.1:10011,127.0.0.1:10012,127.0.0.1:10013,127.0.0.1:10014”
  • CHANGE MASTER TO MASTER_USER=’rpl_user’,MASTER_PASSWORD=’rpl_pass’ FOR CHANNEL ‘group_replication_recovery’;
  • START GROUP_REPLICATION;
    这里不再做演示,还是保持上文中三节点。

    强制移除故障成员

    当故障导致半数以上的成员不可用时,group replication就不能对外提供服务,当这种情况发生时,可以设置指定的成员能够立刻对外提供服务。虽然冗余能力不好,但至少能保障业务不中断。可通过以下设置:
  • SET GLOBAL group_replication_force_members = ip:port,ip:port
    group_replication_force_members中配置的是成员地址的列表,只需要在列表中的任意一个成员上设置即可,这个就是强制group replication用参数中的几个成员来构成组,把其他成员从组中移除出去。

    故障演练

    通过模拟其中一个节点故障后,主节点是否会自动迁移,故障节点恢复后,数据是否会自动同步

    查看当前组复制状态

    登录节点1,查看组复制状态,确认当前节点1为主节点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    [root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock   
    Welcome to the MySQL monitor. Commands end with ; or \g.
    Your MySQL connection id is 30
    Server version: 5.7.20-log MySQL Community Server (GPL)

    Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

    mysql> SELECT * FROM performance_schema.replication_group_members;
    +---------------------------+--------------------------------------+-------------+-------------+--------------+
    | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
    +---------------------------+--------------------------------------+-------------+-------------+--------------+
    | group_replication_applier | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | jiessie | 9001 | ONLINE |
    | group_replication_applier | cf04e66c-d58a-11e7-b97e-00163e00dc49 | jiessie | 9002 | ONLINE |
    | group_replication_applier | d4286108-d58a-11e7-807d-00163e00dc49 | jiessie | 9003 | ONLINE |
    +---------------------------+--------------------------------------+-------------+-------------+--------------+
    3 rows in set (0.00 sec)

    mysql> select * from performance_schema.global_status where variable_name = 'group_replication_primary_member';
    +----------------------------------+--------------------------------------+
    | VARIABLE_NAME | VARIABLE_VALUE |
    +----------------------------------+--------------------------------------+
    | group_replication_primary_member | c0acc2c7-d58a-11e7-b59f-00163e00dc49 |
    +----------------------------------+--------------------------------------+
    1 row in set (0.00 sec)

    mysql>

模拟主节点故障

1
2
mysql> shutdown;
Query OK, 0 rows affected (0.00 sec)

确认当前组复制状态

节点1的9001服务已经不存在,通过登录9002查看组成员状态,9002节点已经成为新节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@jiessie mysql5.7.20]# netstat -tunlp|grep mysql|grep 900                                         
tcp 0 0 0.0.0.0:9002 0.0.0.0:* LISTEN 22034/mysqld
tcp 0 0 0.0.0.0:9003 0.0.0.0:* LISTEN 6059/mysqld
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 28
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | cf04e66c-d58a-11e7-b97e-00163e00dc49 | jiessie | 9002 | ONLINE |
| group_replication_applier | d4286108-d58a-11e7-807d-00163e00dc49 | jiessie | 9003 | ONLINE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
2 rows in set (0.00 sec)

mysql> select * from performance_schema.global_status where variable_name = 'group_replication_primary_member';
+----------------------------------+--------------------------------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+----------------------------------+--------------------------------------+
| group_replication_primary_member | cf04e66c-d58a-11e7-b97e-00163e00dc49 |
+----------------------------------+--------------------------------------+
1 row in set (0.00 sec)

mysql>

组复制新主节点插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mysql> create table test.t2(id int unsigned not null auto_increment primary key,age int);
Query OK, 0 rows affected (0.02 sec)

mysql> insert into test.t2 values(null,111111);
Query OK, 1 row affected (0.00 sec)

mysql> select * from test.t2;
+----+--------+
| id | age |
+----+--------+
| 2 | 111111 |
+----+--------+
1 row in set (0.00 sec)

mysql>

从节点查看数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock   
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 46
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select * from test.t2;
+----+--------+
| id | age |
+----+--------+
| 2 | 111111 |
+----+--------+
1 row in set (0.00 sec)

mysql>

故障节点恢复

故障节点恢复后,登录实例,启动组复制

1
2
3
4
5
6
7
8
9
10
11
[root@jiessie mysql5.7.20]# base/bin/mysqld_safe --defaults-file=/hwdata/data/mysql5.7.20/conf/s1.cnf & 
[6] 22692
[root@jiessie mysql5.7.20]# 2017-12-01T07:35:42.377430Z mysqld_safe Logging to '/hwdata/data/mysql5.7.20/data/s1/jiessie.err'.
2017-12-01T07:35:42.396416Z mysqld_safe Starting mysqld daemon with databases from /hwdata/data/mysql5.7.20/data/s1

[root@jiessie mysql5.7.20]# netstat -tunlp|grep mysql|grep 900
tcp 0 0 0.0.0.0:9001 0.0.0.0:* LISTEN 23135/mysqld
tcp 0 0 0.0.0.0:9002 0.0.0.0:* LISTEN 22034/mysqld
tcp 0 0 0.0.0.0:9003 0.0.0.0:* LISTEN 6059/mysqld
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "START GROUP_REPLICATION;"
[root@jiessie mysql5.7.20]#

再次查看组复制状态及插入数据状态

故障节点已经加入到组复制中,同时故障期间插入的数也已经同步过来,主节点还是节点2的9002实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@jiessie mysql5.7.20]# base/bin/mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 48
Server version: 5.7.20-log MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> SELECT * FROM performance_schema.replication_group_members;
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
| group_replication_applier | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | jiessie | 9001 | ONLINE |
| group_replication_applier | cf04e66c-d58a-11e7-b97e-00163e00dc49 | jiessie | 9002 | ONLINE |
| group_replication_applier | d4286108-d58a-11e7-807d-00163e00dc49 | jiessie | 9003 | ONLINE |
+---------------------------+--------------------------------------+-------------+-------------+--------------+
3 rows in set (0.00 sec)

mysql> select * from performance_schema.global_status where variable_name = 'group_replication_primary_member';
+----------------------------------+--------------------------------------+
| VARIABLE_NAME | VARIABLE_VALUE |
+----------------------------------+--------------------------------------+
| group_replication_primary_member | cf04e66c-d58a-11e7-b97e-00163e00dc49 |
+----------------------------------+--------------------------------------+
1 row in set (0.00 sec)

mysql> select * from test.t2;
+----+--------+
| id | age |
+----+--------+
| 2 | 111111 |
+----+--------+
1 row in set (0.00 sec)

组复制系统变量

变量名 变量范围 动态变量 类型 默认 描述
group_replication_group_name Global Yes string NULL 此server实例所属组名称,须是UUID格式
group_replication_start_on_boot Global Yes boolean ON server是否应在自身启动期间启动组复制
group_replication_local_address Global Yes string NULL 作为本地地址,格式为主机:端口
group_replication_member_weight Global Yes integer 50 可以分配成员的百分比权重,影响成员在发生故障转移时作为主要成员选举的机会
group_replication_group_seeds Global Yes string NULL 提供加入成员的成员列表,其中加入成员需要的数据以获得与该组的同步
group_replication_force_members Global Yes string NULL 以逗号分隔开的组内其他成员的地址列表.此选项用于强制建立新的组成员关系,在此过程中已排除的成员不接收新的视图并且被排除在外.您需要手动移除已排除的server
group_replication_bootstrap_group Global Yes boolean OFF 配置此server以引导组,此选项只能在一个server上设置,且只能在首次启动组或重启整个组时设置.组被引导后,将此选项设置为OFF.
group_replication_poll_spin_loops Global Yes integer 0 在组通信线程等待传入更多的网络信息之前,等待mutex被释放的次数
group_replication_recovery_retry_count Global Yes integer 10 要加入的成员尝试连接到可用数据源节点的次数
group_replication_recovery_reconnect_interval Global Yes integer 60 在组中找不到数据源节点时,重新尝试连接的时间间隔
group_replication_recovery_use_ssl Global Yes boolean OFF 组复制恢复通道是否使用SSL
group_replication_recovery_ssl_ca Global Yes string NULL 包含受信任SSL证书颁发机构列表的文件的路径
group_replication_recovery_ssl_capath Global Yes string NULL 包含受信任SSL证书颁发证书的目录路径
group_replication_recovery_ssl_cert Global Yes string NULL 用于建立安全连接的SSL证书文件的名称
group_replication_recovery_ssl_key Global Yes string NULL 用于建立安全连接的SSL密钥文件的名称
group_replication_recovery_ssl_cipher Global Yes string NULL 用于SSL加密的密码列表
group_replication_recovery_ssl_crl Global Yes string NULL 包含具有证书撤销列表文件的路径
group_replication_recovery_ssl_crlpath Global Yes string NULL 包含具有证书撤销列表文件的路径
group_replication_recovery_ssl_verify_server_cert Global Yes boolean OFF 在恢复过程中用于检查数据源节点发送证书中的”通用名称”
group_replication_recovery_complete_at Global Yes enumeration TRANSACTIONS_APPLIED 状态传输后处理缓存中的事务时的恢复策略.此选项指定成员在”接收到加入群组(TRANSACTIONS_CERTIFIED)之前遗漏的所有事务”或”收到并应用了这些事务(TRANSACTIONS_APPLIED)”之后,是否标记在线
group_replication_components_stop_timeout Global Yes integer 31536000 组复制在关闭时等待每个组件的超时时间,以秒为单位
group_replication_allow_local_lower_version_join Global Yes boolean OFF 即使当前server的插件版本比组的插件版本低,也允许它加入组
group_replication_allow_local_disjoint_gtids_join Global Yes boolean OFF 即使含有组中不存在的事务,也允许当前server加入组,此选项要慎重,可能会破坏组内一致性
group_replication_auto_increment_increment Global Yes integer 7 确定在此server实例上执行的连续事务之间的步长
group_replication_compression_threshold Global Yes integer 1000000 以字节为单位,超过该值将强制执行lz4压缩,当设置为0时,压缩设置无效
group_replication_gtid_assignment_block_size Global Yes integer 1000000 为每个成员保留的连接GTID数量,每个成员在开始时会消耗掉一些,当需要时在获取更多个
group_replication_ssl_mode Global Yes enumeration DISABLED 指定组复制成员之间连接的安全状态
group_replication_single_primary_mode Global Yes boolean ON 设置组自动选择一个server来处理读写工作,这个server是主(primary),所有其他都是从(secondaries)
group_replication_transaction_size_limit Global Yes integer 0 配置组接受最大事务大小,以字节为单位,大于这个事务被回滚.当设置为0时,无限制
group_replication_unreachable_majority_timeout Global Yes integer 0 配置在离开组之前遭受网络分区且无法连接到大多数成员的成员需要等待多长时间,默认情况为0,这意味着由于网络分区而成为少数的成员永远等待连接组.
group_replication_enforce_update_everywhere_checks Global Yes boolean OFF 多主模式下为多主更新或禁用严格一致性检查
group_replication_flow_control_mode Global Yes enumeration QUOTA 指定启用限流模式,不需要重置组复制就可以修改此变量
group_replication_flow_control_certifier_threshold Global Yes integer 25000 触发限流的验证队列的阈值,不需要重置组复制就可以修改此变量
group_replication_flow_control_applier_threshold Global Yes integer 25000 触发限流的应用队列的阈值,不需要重置组复制就可以修改此变量
group_replication_ip_whitelist Global Yes string AUTOMATIC 指定允许哪些主机可以访问组,默认为AUTOMATIC,它允许来自主机上的私有子网的连接,多个以逗号分隔

组复制的多主和单主模式

默认为单主模式,组中的成员不可能以不同的模式部署,例如一个配置为多主模式,另一个配置为单主模式.要切换模式,组和服务器之间需要不的操作配置重新启动.无论部署模式如何,组复制不处理客户端故障切换,必须由应用程序本身,连接器或中间件框架如代理或路由器来处理.

单主模式

在此模式下,该组具有设置为读写械的单主服务器,组中的所有其他成员都设置为只读模式(超级只读模式super_read_only),所有其他加入的节点自动识别主节点并设置为自己为只读.

选主过程

参考官方流程图:
image在单主机模式下,将禁用在多主机模式下部署的某些检查,因为系统会强制每次只有一个写入节点。例如,允许对具有级联外键的表进行更改,而在多主模式下不允许。在主节点故障时,自动选举机制选择下一个主节点。通过按字典顺序(使用其UUID)并选择列表中的第一个节点来排序剩余的节点来选择下一个主节点,可通过group_replication_member_weight此参数影响选主。如果主节点从组中删除,则执行选择,并从组中的其余节点中选择新的主节点,这个选择按照词典顺序排序节点UUID并选择第一个来执行。一旦选择了新的主节点,其他节点将设置为从节点,从节点为只读。

多主模式

在多主模式下,没有单个主模式的概念,也没有选举程序,因为没有节点发挥任何特殊的作用。加入组时,所有服务器都设置为读写模式。
在多主模式下部署时,将检查语句以确保他们与模式兼容,以多主模式部署组复制时进行以下检查:

  1. 如果一个事务在SERIALIZABLE隔离级别下执行,那么当它自己与该组同步时,它的提交失败。
  2. 如果事务针对具有级联约束的外键的表执行,则在与组自身同步时,事务无法提交。
    这些检查可以通过设置选项来禁用 group_replication_enforce_update_everywhere_checks 到FALSE。在单主模式下部署时,必须将此选项设置为FALSE。

    客户端故障转移

    参考官方流程图:
    image

    自增字段的处理

    当使用多主模式时,需要设置autoincrement相关的参数来保证自增字段在每个成员上产生不同的值。group replication提供了两种配置方式,分别如下:
  3. 直接配置MySQL的系统变量,通过命令来配置时,需要在所有成员上配置下面两个参数
  • set global auto_increment_offset = N;
  • set global auto_increment_increment = N;
  1. 通过group replication插件来配置,group replication插件定义了一个新的参数来配置自增字段的大小,上面的参数列表中也有解释
  • set global group_replication_auto_increment_increment = N;
    group_replication_auto_increment_increment的默认值是7。如果自增值的浪费不对业务千万影响,可以不用修改这个值。group replication没有提供新的参数来设置自增值的偏移,而是将MySQL服务器的server-id看作自增值的偏移。如果用户的server-id本来就是按照1、2、3这样的顺序配置的,就不需要再做额外的配置。在启动group replication插件时,它会检测用户是否配置了MySQL的自增变量。如果用户没有配置这两个参数(auto_increment_offset和auto_increment_increment都为1),则会自动将group_replication_auto_increment_increment和server-id的值设置到MySQL的auto_increment_increment和auto_increment_offset全局变量中。
    自增变量将从1开始的连续自增值划分为固定大小的段,各个MySQL服务器分别使用段内不同偏移量的自增值。auto_increment_increment代表段的大小,每个成员都应该配置相同的段大小。而auto_increment_offset是本成员 的自增值在段内的偏移。每个成员应该设置不同的偏移量。假设段的大小设为5,成员A的偏移量设为1,成员B的偏移量设为2,那么成员A上产生的自增值为:1、6、11、16……,成员B上产生的自增值为:2、7、12、17……。如下图所示:
    自增ID示例图
  • | 偏移1 | 偏移2 | 偏移3 | 偏移4 | 偏移5
    —|—|—|—|—|—
    第一段 | 1 | 2 | 3 | 4 | 5 |
    第二段 | 6 | 7 | 8 | 9 | 10 |
    第三段 | 11 | 12 | 13 | 14 | 15 |
    第四段 | 16 | 17 | 18 | 19 | 20 |
    … | … | … | … | … | … |
    可以看出,自增字段的大小依赖于group replication组中的成员的多少。auto_increment_increment最小要等于group replication组内的数量。如果段的大小等于组内的数量,则所有的自增id都会被使用。如果段的大小大于组内的成员的数量,则有一部自增值永远不会被使用,会千万一定的浪费。比如,组内有3个成员,偏移分别是1、2、3,而段的大小是5,那么偏移4和5上的值就会被浪费掉。考虑到group replication组的扩展,最好将段的大小设置得比现有的组内成员数量大一些。这样虽然会浪费一些自增值,但是扩展会很简单。在使用的过程中变更自增字段的大小,处理起来会比较麻烦,所以要提前规划好。
    自增字段演示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    [root@jiessie percona]# mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock -e "show variables like 'server_id';"
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | server_id | 1 |
    +---------------+-------+
    [root@jiessie percona]# mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock -e "show variables like 'server_id';"
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | server_id | 2 |
    +---------------+-------+
    [root@jiessie percona]# mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock -e "show variables like 'server_id';"
    +---------------+-------+
    | Variable_name | Value |
    +---------------+-------+
    | server_id | 3 |
    +---------------+-------+
    [root@jiessie percona]# mysql -uroot -S /hwdata/data/mysql5.7.20/data/s1/s1.sock
    Welcome to the MySQL monitor. Commands end with ; or \g.
    Your MySQL connection id is 37
    Server version: 5.7.20-log MySQL Community Server (GPL)

    Copyright (c) 2009-2017 Percona LLC and/or its affiliates
    Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

    mysql> show variables like 'group_replication_auto_increment_increment';
    +--------------------------------------------+-------+
    | Variable_name | Value |
    +--------------------------------------------+-------+
    | group_replication_auto_increment_increment | 5 |
    +--------------------------------------------+-------+
    1 row in set (0.01 sec)

    mysql> select * from performance_schema.global_status where variable_name = 'group_replication_primary_member';
    +----------------------------------+--------------------------------------+
    | VARIABLE_NAME | VARIABLE_VALUE |
    +----------------------------------+--------------------------------------+
    | group_replication_primary_member | c0acc2c7-d58a-11e7-b59f-00163e00dc49 |
    +----------------------------------+--------------------------------------+
    1 row in set (0.00 sec)

    mysql> SELECT * FROM performance_schema.replication_group_members;
    +---------------------------+--------------------------------------+-------------+-------------+--------------+
    | CHANNEL_NAME | MEMBER_ID | MEMBER_HOST | MEMBER_PORT | MEMBER_STATE |
    +---------------------------+--------------------------------------+-------------+-------------+--------------+
    | group_replication_applier | c0acc2c7-d58a-11e7-b59f-00163e00dc49 | jiessie | 9001 | ONLINE |
    | group_replication_applier | cf04e66c-d58a-11e7-b97e-00163e00dc49 | jiessie | 9002 | ONLINE |
    | group_replication_applier | d4286108-d58a-11e7-807d-00163e00dc49 | jiessie | 9003 | ONLINE |
    +---------------------------+--------------------------------------+-------------+-------------+--------------+
    3 rows in set (0.00 sec)

    mysql> create table test.t0(id int unsigned not null primary key auto_increment,name varchar(20));
    Query OK, 0 rows affected (0.04 sec)

    mysql> insert into test.t0 values(null,'111'),(null,'222'),(null,'333');
    Query OK, 3 rows affected (0.00 sec)
    Records: 3 Duplicates: 0 Warnings: 0

    mysql> select * from test.t0;
    +----+------+
    | id | name |
    +----+------+
    | 1 | 111 |
    | 6 | 222 |
    | 11 | 333 |
    +----+------+
    3 rows in set (0.00 sec)

    mysql> exit
    Bye
    [root@jiessie percona]# mysql -uroot -S /hwdata/data/mysql5.7.20/data/s2/s2.sock
    Welcome to the MySQL monitor. Commands end with ; or \g.
    Your MySQL connection id is 27
    Server version: 5.7.20-log MySQL Community Server (GPL)

    Copyright (c) 2009-2017 Percona LLC and/or its affiliates
    Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

    mysql> insert into test.t0 values(null,'aaa'),(null,'bbb'),(null,'ccc');
    Query OK, 3 rows affected (0.00 sec)
    Records: 3 Duplicates: 0 Warnings: 0

    mysql> select * from test.t0;
    +----+------+
    | id | name |
    +----+------+
    | 1 | 111 |
    | 2 | aaa |
    | 6 | 222 |
    | 7 | bbb |
    | 11 | 333 |
    | 12 | ccc |
    +----+------+
    3 rows in set (0.00 sec)

    mysql> exit
    Bye
    [root@jiessie percona]# mysql -uroot -S /hwdata/data/mysql5.7.20/data/s3/s3.sock
    Welcome to the MySQL monitor. Commands end with ; or \g.
    Your MySQL connection id is 23
    Server version: 5.7.20-log MySQL Community Server (GPL)

    Copyright (c) 2009-2017 Percona LLC and/or its affiliates
    Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

    Oracle is a registered trademark of Oracle Corporation and/or its
    affiliates. Other names may be trademarks of their respective
    owners.

    Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

    mysql> insert into test.t0 values(null,'123'),(null,'456'),(null,'789');
    Query OK, 3 rows affected (0.00 sec)
    Records: 3 Duplicates: 0 Warnings: 0

    mysql> select * from test.t0;
    +----+------+
    | id | name |
    +----+------+
    | 1 | 111 |
    | 2 | aaa |
    | 3 | 123 |
    | 6 | 222 |
    | 7 | bbb |
    | 8 | 456 |
    | 11 | 333 |
    | 12 | ccc |
    | 13 | 789 |
    +----+------+
    3 rows in set (0.00 sec)

    mysql> exit
    Bye
    [root@jiessie percona]#

DDL语句并发执行的问题

多主复制时,通过冲突检测来辨别有冲突的事务,有冲突的事务通过回滚操作来处理。MySQL5.7上的DDL不是原子操作无法回滚,因此group replication没有对DDL做冲突检测。换句话说,DDL语句不会和其他任何语句冲突(包括DML和DDL)。如果DDL和有冲突的语句在不同的成员上同时执行,可能导致错误或数据不一致。假设在成员A上执行如下事务:

  • BEGIN;
  • INSERT INTO t1 values(1);
    此时,成员B上执行以下语句:
  • TRUNCATE TABLE t1;
    成员A上接着执行:
  • COMMIT;
    成员A上两个事务的执行顺序是:
  • INSERT INTO t1 VALUES(1);
  • TRUNCATE TABLE t1;
    成员B上两个事务的执行顺序是:
  • TRUNCATE TABLE t1;
  • INSERT INTO t1 VALUES(1);
    这就会导致两个成员上数据的不一致。因此多主模式下,执行任何DDL前,要将可能有冲突的事务迁移到同一台MySQL服务器上,再开始执行DDL语句。

    组复制基本原理

    状态机复制

    本质上来说,group replication是一个状态机复制的集群。在状态机复制的架构中,数据库被当作一个状态机。每一次写操作都会导致数据库的状态变化。为了创建一个高可用的数据库集群,有一个组件将这些操作按照同样的顺序发送到多个初始状态一致的数据库上,让这些数据库执行同样的操作。因为初始状态相同,每次执行的操作也相同,所以每次状态变化后各个数据库服务器上的数据保持一致。下图是一个非常简单的状态机复制的示意图。
    image
    假设图中数据库DB1、DB2和DB3的初始状态是相同的,事务T1、T2和T3分别如下:
  1. #T1

    DELETE FROM t1 WHERE pk = 1;

  2. #T2

    UPDATE t1 SET c1 = 100 WHERE pk = 1;

  3. #T3

    UPDATE t1 SET c1 = 50 WHERE pk = 1;
    3个客户端并发地将这3个事务发送到事务分发器上,事务分发器首先对这3个事务进行排序 ,然后按照同样的顺序(T3、T1、T2)并发地发送到DB1、DB2和DB3这3个数据库上去执行。T3最先在DB1、DB2和DB3上执行,执行完后,DB1、DB2和DB3上的结果相同。接着T1被执行,执行后DB1、DB2和DB3上t1表中pk为1的记录都被删除了。最后T2被执行,由于T1删除了记录,因此T2执行时会失败。T2在DB1、DB2和DB3上都会失败,所以DB1、DB2和DB3的数据仍然是一致的。

    分布式的状态机复制

    在上图的模型中,事务分发器是一个单点故障点。为了避免单点故障,可以采用分布式的状态机复制。在分布式的状态机复制中,有多个事务分发器,它们彼此相互通信。事务分发器可以同时接收事务请求,就像单个事务分发器同时接收事务请求一样。从应用层面来说并发的事务发到同一个事务分发器和发到不同的事务分发器上效果是一样的。事务分发器之间会相互通信,把所有的事务汇总、排序。最终,每个事务分发器上都有一份完整的排好序的事务请求。每个事务分发器只连接到一台数据库服务器上,并负责把事务请求依次发送到这个相连的数据库上去执行。下图所示的是分布式状态机复制的一个模式。
    image
    数据库DB1、DB2和DB3的初始状态是相同的,事务T1、T2和T3分别如下:
  4. #T1

    DELETE FROM t1 WHERE pk = 1;

  5. #T2

    UPDATE t1 SET c1 = 100 WHERE pk = 1;

  6. #T3

    UPDATE t1 SET c1 = 50 WHERE pk = 1;
    3个客户端并发地将这3个事务发送到各自的事务分发器上。事务分发器彼此之间互相通信,最终每个事务分发器上都会有一份完整的顺序一样的事务请求列表(T3、T1、T2)。3个事务分发器将这些事务请求按照顺序发送到各自的数据库服务器上去执行。T3最先在DB1、DB2和DB3上执行,执行完后DB1、DB2和DB3上的结果相同。接着T1被执行,执行后DB1、DB2和DB3上t1表中pk为1的记录都被删除了。最后T2被执行,由于T1删除了记录,T2在DB1、DB2和DB3上都会失败,所以DB1、DB2和DB3的数据仍然是一致的。
    不管是单个事务分发器还是多个事务分发协同工作,它们的目标都是一样的:将所有的事务按照同样的顺序发送到数据库服务器上去执行。

    分布式的高可用数据库

    如果将上图中的事务分发器集成到数据库系统中,就变成了一个分布式的高可用数据库系统,如下图所示:
    image
    用户通过数据库服务器的用户接口执行事务。数据库服务器收到事务请求后,首先交由事务分发模块处理。事务分发模块将事务汇总排序,然后依次交由数据处理模块去执行这些事务。如果去年内部的细节,就会发现这是一个非常简洁的数据库集群方案。通过group replication构建的MySQL集群就是这样一个分布式的高可用MySQL系统。整个集群的架构如下图所示:
    image

    深入理解组复制中事务的执行过程

    从上文中的模型可以知道,group replication插件中最重要的功能就是事务分发器的功能。group replication中实现的事务分发器和模型中的略有不同。在模型中,假设事务分发器分发的是事务的SQL语句,也就是说事务分发器的处理是在事务执行前。而在group replication中,事务分发器分发的是Binlog Event,事务分发器的处理是在事务执行即将结束的时候。group replication将这称作乐观的事务执行策略,可以带来更好的性能。但是在这种策略下,多个成员的事务可能发生冲突。group replication需要一个冲突检测机制来发现并处理冲突。由于Binlog Event的执行和SQL语句的执行过程是不一样的,group replication就用到异步复制中Binlog Event的执行模块。为了更好地理解group replication事务执行的过程,这里将group replication处理的事务分为本地事务和异地事务。本地事务指的是用户Session中或异步复制线程中执行的事务。异地事务指的是group replication中传播的由Binlog Event构成的事务。
    根据事务处理过程中的不同处理步骤,group replication中事务分发器的功能划分为以下四部分。
  • 本地事务控制模块
  • 成员间的通信模块
  • 全局事务认证模块
  • 异地事务执行模块

    本地事务控制模块

    MySQL通过API向插件提供了事务执行过程中几个重要阶段的监控接口,group replication通过这些接口来监控和控制接口的执行。MySQL的事务在提交时,内部会分为三个阶段:准备(prepare)阶段、记录Binlog文件阶段和提交(commit)阶段。group replication对本地事务的控制逻辑在before_commit这个接口中执行。before_commit是在事务的prepare阶段之后,写Binlog文件阶段之前被执行的。对本地事务的控制包括以下三个步骤:

    发送事务信息

    group replication首先会将事务执行的相关信息打包,通过通信模块的接口发送给本地的通信模块。本地事务控制模块只发送信息,不接收任何信息。因此,这个通信是异步过程。只要本地的通信模块接收了消息就返回成功。发送到其他成员是通信模块的职责。事务信息包括主键信息、数据库快照版本和事务产生的Binlog Events。
  • 主键信息是Server层生成Binlog Event的时候一同生成的。是否记录主键信息是通过transaction_write_set_extraction变量来控制的。主键信息中记录的并不是主键字段的值,而是字段值加上库名、表名等哈希值。
  • 数据库快照版本是当前MySQL的全局变量gtid_executed的值。它包含了当前事务提交时所有已经执行了的事务的GTID,代表了当前事务执行时数据库的状态,因此称为数据库的快照版本。
  • 当发送事务信息时,Binlog Event还没有写入Binlog文件。因此,Binlog Event是从当前线程的Binlog Cache中获取的,而不是依赖于Binlog文件。
  • Transaction_context_log_event,本地成员的UUID、主键信息和数据库快照版本会被封装进Transaction_context_log_event中,和事务产生的Binlog Event一起发送出去。Transaction_context_log_event放在其他Binlog Event前面。

    等待全局事务认证模块的认证结果

    在事务信息发送成功后,事务会被阻塞,开始等待全局事务认证模块的认证结果。事务认证完成后,全局事务认证模块会唤醒当前事务的线程,让事务继续执行。

    认证结果的处理

    这个过程实际上是由MySQL的代码执行的,而不是由group replication的代码执行的。在哪里执行的并不重要,只要理解这个过程就好。事务继续执行后,会检测 认证结果。如果认证成功,就继续提交事务,如果认证失败就会返回错误。然后由MySQL来执行Rollback的逻辑。

    成员间的通信模块

    通信模块分为三部分,如下图所示:
    image
  1. 本地数据接收部分负责接收本地成员上其他模块的数据发送请求,接收到的数据包被放入本地数据队列等待处理。
  2. 成员间的通信部分负责和其他成员通信。通信工作包括:从本地数据队列读取数据包发送给其他成员,以及接收其他成员发送过来的数据包。各个成员之间的通信使用了Paxos协议,Paxos协议是一个分布式的一致性协议。
  3. 全局数据包发送部分将所有的数据包顺序返回给本地成员上的全局事务认证模块。
    当各个成员的通信模块接收到上层模块的数据发送请求时,这些并发的数据请求是无序的,如下图所示:
    image
    3个成员分别有1个数据包,分别是T1、T2和T3产生的数据包。这些并发、无序的数据包会通过Paxos协议汇聚到每个成员上,并且排序。最终每个成员的通信模块都会拥有同样的数据库包,这些数据包会按照同样的顺序发送到各自成员上的全局事务认证模块,如下图所示:
    image
    Paxos协议的工作核心就是对所有数据包进行汇聚和排序。为了完成上面的功能,Paxos协议本身会进行三次TCP通信,如下:
  • 发送数据包给其他成员的通信模块
  • 其他成员的通信模块回应收到的数据包
  • 当超过半数的通信模块(包括它自己)回应后,发送消息告诉所有节点这个数据包同步成功。只有当Paxos协议的三个步骤成功完成后通讯模块才会把这个数据包发送给全局认证模块。这就像是两个公司谈合作经办人多轮协商把所有的条款都谈妥没有异议了,然后才通知老板准备签约。
    为了保证数据按照同样的顺序发送到全局事务认证模块,Paxos协议还会为每个同步成功的数据包分配一个唯一的ID,当各个成员上的通信模块向上层模块发送这些数据包时,会按照数据包的ID以小到大的顺序发送给全局认证模块。这个ID由两部分组成,分别如下:
  • 成员序号:每个成员在加入组后,就会获得一个组内成员的唯一序号,这个成员发出的所有数据包都会使用这个成员序号。
  • 自增序号:每个成员的通信模块都会维护一个自增的序号,按照发送数据包的顺序,给每个数据包分配一个顺序号。
    如图所示,假设一个组有三个成员,那么顺序号为1的成员上的数据包ID是1:1,2:1,…,N:1。顺序号为2的成员上的数据包ID是1:2,2:2,…,N:2。顺序号为3的成员上的数据包ID是1:3,2:3,…,N:3。
    image
    当向全局认证模块发送这些数据包时,必须按照ID连续发送,不能跳过某个ID的数据包。本质上来说,这是一个轮训(Round Robin)的过程。先发送成员1的第一个数据包,接着发送成员2的第一个数据包,然后发送成员3的第一个数据包,如图所示。
    image
    如果在这个过程中,成员2上的数据包还没有同步成功呢?这时候就得等着,直到成员2的第一个数据包同步成功的后,才能返回给全局认证模块,然后继续返回成员3的数据包。假如成员2是空闲的,且本地没有任何事务处理呢?这种情况下,成员2需要发一个空操作指令(NOP),告诉其他成员跳过ID1:2的数据包。这个指令是当成员2收到ID1:1的数据包同步成功的消息(Paxos的第三个TCP消息)后发出的,如图所示。
    image
    以上介绍的只是Paxos协议很少的一部分,如果想详细了解Paxos协议,还是要查阅更多的资料。总结一下,Paxos在通信上的特别如下:
  • 数据包同步成功需要三次TCP传输。
  • 每个数据包都要发送到所有的成员上,因此需要传输多份,传输的数据量会被放大。
  • 假设数据包发送到其他所有成员的过程是并发进行的,那么数据包同步成功需要的时间是成员间最慢的那条链路上完成三次TCP通信的时间。
    这些特点决定了group replication在延迟大、带宽窄的网络中的效率会是比较低的。group replication为了提高Paxos对网络的适用性、做了以下优化:
  • 使用了LZ4压缩算法对事务信息进行压缩,当数据包的大小超过一个阈值时会被自动压缩。
  • Paxos会将多个事务信息封装到一个数据包内进行通信,大大减少了Paxos通信的次数。

    全局事务认证模块

    全局事务认证模块有一个消息队列,用来存放所有收到的消息。这些消息主要是事务的Binlog Event,也有一部分状态和控制信息。状态表replication_group_member_stats中的字段COUNT_TRANSATION_IN_QUEUE指的就是这个队列中事务数据。
    全局事务认证模块的核心任务是做冲突检测。冲突检测是指识别出那些同时修改了同样数据的事务,并做出相应的处理。冲突检测中需要的信息包括如下三点:
  • 主键信息。
  • 事务执行时的数据快照版本。
  • 执行事务的MySQL服务器的UUID。

    冲突检测需要的信息

    group replication的冲突检测中以数据行为单位,两个事务是不是修改了同样的数据,是通过事务所修改的主键值来判断的。上方中有介绍,Server层在产生Binlog Event时,会同时记录所修改的主键信息(库名、表名主键等信息产生的哈希值)。当发现两个事务修改了同样的数据后,如何来判断这两个事务是不是同时执行的呢?这里用到了数据库快照版本。数据库快照版本是数据库的一个瞬时状态,每个写操作都会导致数据库的状态变化,不同的状态用不同的快照版本来表示。快照版本是用GTID来表示的,每个写事务都会产生一个唯一的GTID,这个GTID由全局事务认证模块产生,且在事务提交时会被添加到全局变量gtid_executed中。因此,gtid_executed的内容就是MySQL的数据库快照版本。
    如下图所示,为数据库快照版本变化的示例图。图中,事务T1开始执行时所基于的数据库快照版本为空,T1提交后的数据库版本变为”group_name:1”,group_name是group replication组的UUID。事务T2的执行则基于”group_name:1”的快照版本,这个数据库的快照版本,主键信息及其MySQL服务器的UUID一起被封装到Transaction_context_log_event上,和其他事务产生的Binlog Event一起发送到全局事务检测模块。
    image

    冲突检测数据库

    全局事务认证模块中还维护了一个冲突检测数据库,它是主键哈希+快照版本的列表。快照版本存储的是最后一个修改此主键信息的快照版本加上这个事务的GTID。收到事务信息后,全局事务认证模块会根据Transaction_context_log_event中的主键信息,从冲突检测数据库中检索出所有主键的快照版本和该事务的快照版本进行比对。当前事务的快照版本必须要包含检索出的所有主键快照版本中的GTID,否则就是有冲突。

    理解冲突检测的过程

    下面通过一个例子来理解冲突检测的原理,如图所示:
    image
    假设当前所有成员的数据库状态是一致的,它们的gtid_executed值都为”group_name:1-100”。这时,DB1上执行了事务T1,T1看到数据库快照版本就是”group_name:1-100”。

    UPDATE t1 SET c2 = 5 WHERE pk = 1;
    同时,DB2执行了事务T2,T2看到数据库快照版本就是”group_name:1-100”。
    UPDATE t1 SET c2 = 10 WHERE pk = 1;
    经过通信模块排序后,T2排在了T1前面,如图所示:
    image
    因此,在所有成员全局冲突模块中都是先处理T2,再处理T1。在处理T2时,首先根据Transaction_context_log_event中的主键信息从冲突检测数据库中查找该主键上一次被修改后的快照版本。有可能查到,也不可能查不到。查不到就意味没有冲突。假设冲突检测数据库中的内容如下图所示(为了理解方便,图中主键哈希值用主键值代替)
    image
    从冲突检测数据库中查到此主键上一次被修改后的版本快照是”group_name:1-98”。和T2的快照版本比较得知,T2的快照版本中的GTID集合包含了冲突检测数据中查找到的快照版本的GTID集合。也就意味着,T2在B成员上执行时,上一次该主键的更新已经在B成员上执行了,所以T2和上一次该主键的更新之间是不冲突的。接着,全局事务认证模块会为T2分配一个GTID,并更新冲突检测数据库中此主键的快照版本。假设T2分配的GTID是”group_name:101”,更新后的冲突检测数据库如下图所示:
    image
    T2处理完成后,开始下一个事务T1的冲突检测。首先,也是从冲突检测数据库中查找该主键的快照版本,查到的结果是”group_name:101”,和T1的快照版本对比发现,T1的快照版本不包含”group_name:101”,这就可以判定T1和之前一次的主键修改(T2的修改)是同时进行的,是冲突的。

    冲突处理

    冲突检测完成后,全局认证模块接下来的处理是有本地事务和异地事务区分的。Transaction_context_log_event中记录了产生这个事务的MySQL服务器的UUID。根据这个UUID,就能判断出这是一个本地事务还是异地事务,对于本地事务的处理如下:
  • 如果没有冲突,唤醒这个事务的线程,并且告诉它完成提交操作。
  • 如果有冲突,唤醒这个事务的线程,并且告诉它发生冲突,需要回滚。
  • 不论是否有冲突,Binlog Event都会被丢弃。
    对于异地事务的处理如下:
  • 如果没有冲突,将这个事务的Binlog Event写入Relay log中,让group_relication_appliter通道去执行。
  • 如果有冲突,则丢弃这个事务的Binlog Event。

    冲突检测数据库的清理

    随着使用时间的越来越长,冲突检测数据库中维护的主键信息会越来越多,这些主键信息将占用巨大的内存。为了减小内存的使用并提高查询效率,全局事务认证模块需要定期的清理冲突检测数据库。如果一个事务已经在所有成员上执行了,其他事务的执行肯定不会和它有冲突,因此这个事务的所有主键信息就可以从冲突检测数据库中移除。主键信息的清理是依据各个成员上的全局变量gtid_executed的GTID集合来做的。全局认证模块启动了一个广播线程,每60s将自己的gtid_executed中的GTID集合广播到所有的成员上。全局事务认证模块收到所有成员的GTID后,取它们的交集。这个交集中包含的就是那些已经在所有成员上执行了的事务GTID集合,称作全局完成的GTID集合。全局事务认证模块会将冲突检测数据库所有主键的快照版本和全局完成的GTID集合进行比对,如果快照版本中的GTID集合是全局完成的GTID集合的子集,则这个主键信息就会从冲突检测数据库中清除掉。
    replication_group_member_stats表中的TRANSACTION_COMMITED_ALL_MEMBERS显示的就是全局完成的GTID集合。

    计算基于主键的逻辑时钟

    在执行Binlog Event时,group replication采用了基于主键的并发机制。在这种机制中通过主键来判断是否修改了相同的数据,各种情况如下:
  • 修改了不同数据的事务会安排并发执行。
  • 修改了相同数据的事务会安排顺序执行。
  • DDL不能和任何事务并发执行,必须等待它前面的所有事务执行完毕后才能开始执行,后面的事务也必须要等待DDL执行完毕后,才能开始执行。
    基于主键的并发机制,刚好可以通过逻辑时钟的方式来表达。因此group replication重用了多线程复制中Logical_clock并发复制的功能。基于主键的并发实现起来很简单,只需要根据主键信息计算出事务的逻辑时间,并更新Gtid_log_event中听last_committed和sequence_number的内容即可。group_replication_applier在执行这些事务时,按照Logical_clock方式进行并发就可以了,不需要任何改动。由于需要用到主键信息,因此计算过程是在冲突检测的过程中完成的。全局事务认证模块维护了一个全局事务的顺序号,它会给每个认证成功的事务分配一个顺序号。这个顺序号就是事务的sequence_number,会存储到冲突检测数据库中。当前事务的last_committed的计算和存储在冲突检测数据库中的sequence_number有关。首先,从冲突检测数据库中找出该事务修改的所有主键的sequence_number,即上一次被修改时的sequence_number,取其中的最大值来作为当前事务的last_committed。
    假设正在处理的事务如下:

    UPDATE t1 SET c2 = 5 WHERE pk=1 OR pk =2;
    冲突检测数据库中的顺序如下图所示:
    image
    那么该事务的last_committed是120(120和33的最大值)。如果查不到上次记录,则将last_committed设置为上一次DDL的sequence_number,这保证DDL后面的事务不会和DDL并发执行。如果当前事务是DDL,则last_committed会设置成它自己的sequence_number-1,从而保证DDL前面的所有事务都被执行完后它才会被执行。

    异地事务执行模块

    为了执行异地事务的Binlog Event,group replication会自动创建一个名为group_replication_applier的通道。这个通道的Receiver线程是关闭的,不会从其他成员上去复制Binlog Event。所有的Binlog Event都是由全局事务认证模块通过API写入Relay log的。Binlog Event的执行过程和异步复制没有区别,但是在执行过程中group_replication_applier所执行的事务使用了特权行锁。在对数据加行锁的时候,如果group_replication_applier发现有本地事务已经对数据加了行锁,那么group_replication_applier不会等待本地事务执行完毕,而是立刻将本地事务回滚掉,然后继续执行;而本地事务的Session则会返回错误。这其实很好理解,本地事务和group_replication_applier更新了同样的数据,而且是同时执行的,因此才会返回错误。即便本地事务到了全局事务认证模块,也会因此检测出冲突而被回滚掉。相比而言,由前者回滚本地事务效率更高。

    事务流程的总结

    事务在group replication中的执行过程可以总结为三个部分:
  • 网络传输。
  • 事务在本地的执行过程。
  • 事务在异地的执行过程。

    网络传输

    group replication通过Paxos协议来传播事务信息。Paxos保证所有的事务信息按照同样的顺序传播到所有的成员上,如下图所示:
    image

    事务在本地成员上的执行过程

    事务在本地提交时(prepare之后,写Binlog之前),将事务信息发送至通信模块,然后开始等待事务认证结果。通信模块将事务排序后发送到本地成员的全局认证模块。全局认证模块做完冲突检测后,唤醒该事务继续执行(如果认证成功)或回滚(如果认证失败),如下图所示:
    image

    事务在异地的执行过程

    通讯模块将事务排序后发到全局事务认证模块。全局认证模块认证成功后,将该事务的Binlog Event写入Relay log,由group_replication_applier通道来执行。如果全局事务认证失败,则会丢弃该事务的Binlog Event,如下图所示:
    image

    深入理解成员加入组的过程

    group replication的主要逻辑可以划分为两部分:事务的执行逻辑和成员管理逻辑。上面已经介绍了事务的执行逻辑,下面来介绍成员的管理逻辑。

    组视图

    成员管理中有一个很重要的概念叫作组视图(Group View),或者简称为视图(View)。视图是指group replication组在一段时间内的成员状态。在这个时间段内没有成员变化,即没有成员加入也没有成员退出。如果成员发生了变化,成员状态就变化了,于是它就进入了另外一个视图。不同的视图之间通过视图ID(View ID)来进行区分。视图随时间的变化有先后顺序,因此View ID也是有先后顺序的。View ID被定义为两个部分,分别如下:
  • 前缀部分(固定部分):前缀部分是一个随机数。这个值在组初始化时产生。之后,所有View的前缀部分都使用这个随机数,因此也被称为固定部分。
  • 顺序号部分:顺序号部分是数值。初始化时,第一个视图的顺序号从1开始,以后每次视图变化顺序号递增。
    用户可以通过replication_group_member_stats表查看当前的View ID。View ID显示格式如下:

    14870305055874300:2

    加入组时视图的切换

    当一个MySQL服务器加入组中,group replication插件首先会根据group_replication_group_seeds的内容和一个成员(种子成员)建立TCP连接。而种子成员会根据自己的IP白名单检查是否允许其接入。连接建立后,新成员会发送一个加入请求给种子成员,如下图所示:
    image
    收到请求后,种子成员广播视图变化的消息给所有成员(包括申请加入的成员),如下图所示:
    image
    各个成员收到消息后开始做视图切换。首先,每个成员都会广播一个状态交换消息出去,如下图所示:
    image
    接着,各个成员开始接收状态交换消息。状态交换消息中包含了成员的当前状态和信息。成员收到状态交换信息后,将消息中的成员信息更新到自己的成员列表中。当收到所有成员中的最后一个状态交换消息时,通信模块将完整的新视图以视图数据包的形式返回给全局事务认证模块进行处理。整个视图的切换过程到此结束,视图切换的整个过程不影响在线成员对外提供服务。
    image
    离开组时的视图切换和加入组的视图切换过程基本一样,略。

    View_change_log_event

    状态交换消息和事务数据包一样都是通过Paxos协议发送的,因此它们之间也是有序的。事务数据包以视图数据包为分界线,划分到不同的视图中。它前面的事务数据包属于前一个视图的事务,而后面的数据包都是属于当前视图中的事务。全局事务认证模块会对每一个视图数据包创建一个View_change_log_event,这个Event会被写入Relay log,然后被执行,最终会出现在Binlog中,因此Binlog里的Event也被View_change_log_event划分到不同的视图中。View_change_log_event里面记录了当前的View ID,还有冲突检测数据库的信息。当记录View_change_log_event到Binlog中时,它会被封装到一个事务中,并且有自己的GTID,包括几个Events。

    Gtid_log_event;
    Query_log_event(“BEGIN”)
    View_change_log_event;
    Query_log_event(“COMMIT”)

    恢复

    视图切换完成后,成员就正式加入组内。它可以收到当前视图任何事务的数据包,但是视图切换前的数据包是收不到的。所以,在MySQL服务器加入组后,不能立即对外提供服务,而需要执行一些操作将自己缺失的数据从其他成员上复制过来。这个过程叫作恢复(Recovery)。一个成员加入组后,会立即将自己的状态设置为RECOVERING,开始执行恢复的过程。恢复过程分为本地恢复、全局恢复和缓存事务执行三个步骤。

    本地恢复(Local Recovery)

    如果这个成员曾经加入过这个组,它的group_replication_applier通道的Relay log中可能还有一些Event没有被执行到数据库中。因此,group replication插件首先会启动group_replication_applier通道,将本地的Binlog Event执行完毕。

    全局恢复(Global Recovery)

    本地恢复执行完毕后,开始进行全局恢复。全局恢复是通过group_replication_recovery进行的。group replication插件会随机选择一个在线成员作为这个通道的Master,这个被选择的成员叫作Donor。group replication插件会自动配置group_replication_recovery通道,并且启动这个通道进行复制,复制时使用的是GTID复制。全局恢复有容错能力,如果group_replication_recovery通道的接收线程(Receiver)无法连接到当前的Donor或当前Donor上的Binlog已经删除,则会选择其他成员作为Donor进行复制。在启动group_replication_recovery通道时,group replication插件会告诉它:当碰到View ID等于当前View ID的View_change_log_event时停止。当执行完到这个View_change_log_event后,group_replication_recovery通道会将空上Event中的冲突检测数据库初始化到group replication插件中,然后停止运行,如下图所示:
    image

    缓存事务执行

    在执行全局恢复过程的同时,全局事务认证模块还会收到当前视图中产生的事务数据包。这些数据包会被缓存起来直到全局恢复完毕后,全局事务认证模块才开始处理这些事务。当缓存的事务全部执行完毕后,该成员才能设置为ONLINE状态。当然用户也可以让成员在缓存的事务执行之前上线。group replication提供了参数group_replication_recovery_complete_at来控制上线时间。上线不仅仅是状态的改变,在多主模式时,上线意味着只读模式会被关闭,上线后成员就能够接收用户的写操作了。

    手动恢复

    虽然group replication的恢复过程是很自动化的,但是并不完美,在有些情况下还是需要手动进行恢复的。
  • 当group replication运行了很长时间后,以前的Binlog可能已经删除了,这时就无法自动通过全局恢复来复制数据。
  • 当要复制的Binlog很大时,使用异步复制的全局恢复效率比较低,不能短时间内完成。
    当要加入组内的MySQL服务器符合以上情况时,可以先手动将数据库恢复到一个比较接近的时间点,然后再加入group replication组。

    运维相关问题

    故障切换

    目前MySQL官方没有发布连接组复制专用的客户端(如MongoDB连接复制集的客户端),在实际的应用中如果发生故障,需要客户端自己来处理。对于单主模式的话,如果主节点发生故障,客户端需要判断新的主节点是谁,然后把写切换到新的主节点,基本上和当前的异步同步的主从切换一样,并且新的主节点是集群自动产生,不可控;多主模式需要在客户端进行节点可用性检查,当其中的一个写节点不可用时自动使用其他可用节点。

    大事务支持问题

    目前版本测试并发进行大数据操作和DDL操作时,kill掉大事务,有几率造成集群不可用;在insert into …….select……limit……这种大事务支持不好,可能造成集群不用;多主模式进行DDL操作需要集群内所有节点都为ONLINE状态才可执行,处于ERROR和RECOVERING状态时有几率导致集群堵塞,严重时集群不可用。

    备份问题

    在组复制集群其中的一个节点上执行数据库备份时,不管使用mysqldump(这个不能使用–single-transaction参数,生产中不建议使用mysqldump备份集群数据)或是使用xtrabackup的QPS下降40%,并且备份节点基本停止读写。在测试备份文件导入数据时,多主模式要比单主模式慢。推荐使用组复制+异步复制方式,在异步复制的从节点上进行数据库备份。

    二进制日志删除问题

    因为组复制同步还是基于二进制日志来进行同步的,清理某个节点bin-log时,必须判定这个日志文件是否还在使用,如果在使用,则绝对不能删除,如果删除,则整个集群直接ERROR。

    同步延迟问题

    目前MySQL5.7.20的版本中无法直观查看节点同步延迟,也无法获取延迟多少,不管是时间或事物数,这个打开MySQL的Debug模式,可以获取到节点的延迟事务情况。
    组复制的延迟对集群是有影响的,一旦出现延迟(默认延迟25000个事务),则启动流量控制(Flow Control),每个周期性能衰减当前的10%,直到集群不可用(但集群节点状态为online),单个节点慢整个集群全慢。
    集群中的每个节点都会验证并应用该组提交的事务,有关校验和应用程序过程的统计信息对于了解应用程序队列如何增长,已找到多少冲突,检查了多少事务,在哪里提交了哪些事务等等非常有用。表 performance_schema.replication_group_member_stats 提供与事务认证过程的相关信息,但没有延迟信息。相关字段解释请参考文件

    数据一致性问题

    不管是多写还是单写,都并非是强一致性,均允许有延迟,他在校验完事务是否冲突后把当前广播到各个节点并确定各个节点收到事务后即进入下一个事物的冲突检测,此时每个节点只是拿到了所有事务的执行序列,保证了事务最终顺序执行,从而保证数据的最终一致性,但同一时刻并非强一致性的。

    节点故障脑裂问题

    节点越多性能损耗越大,三个节点比较合适。节点故障可能有脑裂等问题:如5个节点分布在两个机房,机房间网络断掉分为两个部分,2个集群的机房不可用,3个节点的可用,而三个节点的机房网络有问题,此时如果想使两个节点的机房可用,需要重新对两个节点做集群重组,三个节点的就无法恢复到两个节点中去;三节点中其中一个节点宕机,其他两个正常节点可用,故障节点重启没有加入到集群时,此时这个节点以单实例存在可读写,此时会发生脑裂。
    ### 弹性扩展问题
    MySQL官方网站提到了组复制的弹性自动扩展,经过实际测试,这种扩展在生产中是不现实的。可用于生产的弹性扩展要求新加入一个集群,集群中的数据完全由集群来完成自动同步,但由于组复制是基于二进制日志来进行同步的,生产中是不可能完整保留全部的二进制日志,在新加入的节点需要先备份出集群的全量数据,然后根据同步位置去追事务达到数据的一致后节点状态online状态,其实和之前异步同步搭建主从一样。并且官方提示如果恢复时的延迟过大,可能也无法正常达到追到最新数据的位置。

    客户端连接问题

    官方说明中关于故障处理的时候有一句话:组复制不处理客户端故障切换,它必须由应用程序本身,连接器或中间件框架(如代理或路由器)处理。

    FAQ

    参考官方文档

    总结

    目前MGR还不太成熟,但为MySQL的发展指明了方向,相信在未来的MySQL版本中,MGR能够越来越完善.

Aliyun ECS搭建MHA+Keepalived+VIP+MySQL5.7

发表于 2017-10-11 | 分类于 集群高可用
字数统计: | 阅读时长 ≈

前言

随着云服务的迅速发展,越来越多企业选择将服务托管在云服务中,在数据库领域,AWS RDS、Aliyun RDS等都是不错的选择,默认已经做了高可用,基础运维,可以为企业节省不少的运维成本。由于RDS物理数据、Root权限等其他对象对用户不开放,难免在自动化运维中有些壁垒。本文将围绕在Aliyun ECS结合MHA做MySQL的高可用,ECS不支持VIP,但阿里的产品高可用虚拟IP(HaVip)结合keepliaved等第三方软件可间接实现ip的漂移,可满足我们的需求。

MHA简介

MHA是由日本MySQL专家youshimaton(现就职于Facebook公司)用Perl写的一套MySQL故障切换方案,以保障数据库的高可用性。在MySQL故障切换过程中,MHA能做到在0~30s之内实现主MySQL故障转移。
该软件由两部分组成:MHA Manager(管理节点)和MHA Node(数据节点)。MHA Manager可以单独部署在一台独立的机器上管理多个master-slave集群,也可以部署在一台slave节点上。MHA Node运行在每台MySQL服务器上,MHA Manager会定时探测集群中的master节点,当master出现故障时,它可以自动将最新数据的slave提升为新的master,然后将所有其他的slave重新指向新的master。整个故障转移过程对应用程序完全透明。

高可用虚拟IP限制

  • 每个VPC中最多只能同时存在5个vip对象
  • 目前VPC中的网络通信不支持多播和广播,只支持单播;所以,如果用户是使用keepalived之类的第三方软件实现高可用,需要通过配置文件把通信方式改成单播;网上可以找到相应的方法
  • 如果是使用keepalived之类的第三方软件,需要把信条消息的源IP改成ECS的私网IP(而不要用HaVip的私网IP进行心跳检查),不然很容易造成脑裂
  • 当HaVip与EIP绑定后,进行公网通信时,持有HaVip的ECS实例应该通过HaVip的私网ip进行公网通信(而不是ECS自己的私网IP);因为这时EIP是映射在HaVip的私网IP上,而不是映射在ECS的私网IP上
  • 类似的,当使用HaVip做自建SNAT网关的高可用时,SNAT实例上配置的SNAT规则中,source IP应该是havip的私网IP而不是自己的private IP

准备

操作系统:Linux Centos 6.5
master:192.168.16.80
slave: 192.168.16.81
vip: 192.168.16.82 (注:需要和HaVip的私网IP一致)
其中,使用两台机器,分别部署主库和从库,MHA默认部署在从库上,下方中的VIP通EIP,最终是要把EIP和HaVip绑定在一起

系统初始化修改

修改主机名

主库修改主机名

1
2
3
4
5
[root@master ~]# cat /etc/sysconfig/network|grep HOSTNAME
HOSTNAME=master
[root@master ~]# cat /etc/hosts|grep master
192.168.16.80 master
[root@master ~]#

从库修改主机名

1
2
3
4
5
[root@slave ~]# cat /etc/sysconfig/network|grep HOSTNAME
HOSTNAME=slave
[root@slave ~]# cat /etc/hosts|grep slave
192.168.16.81 slave
[root@slave ~]#

设置防火墙

主库设置,允许从库访问

1
2
3
[root@master ~]# /etc/init.d/iptables status|grep 192.168.16.81
11 ACCEPT all -- 192.168.16.81 0.0.0.0/0
[root@master ~]#

其中,192.168.16.81是允许从库的访问规则

从库设置,允许主库访问

1
2
3
[root@slave ~]# /etc/init.d/iptables status|grep 192.168.16.80
11 ACCEPT all -- 192.168.16.80 0.0.0.0/0
[root@slave ~]#

其中,192.168.16.80是允许主库的访问规则

关闭SELINUX

主库查看

1
2
3
[root@master ~]# cat /etc/sysconfig/selinux |grep -v '#' |grep 'SELINUX=' 
SELINUX=disabled
[root@master ~]#

从库查看

1
2
3
[root@slave ~]# cat /etc/sysconfig/selinux |grep -v '#' |grep 'SELINUX=' 
SELINUX=disabled
[root@slave ~]#

建立SSH无密码登录

主库设置

修改服务器/etc/ssh/ssh_config文件,把参数GSSAPIAuthentication修改为no
修改服务器/etc/ssh/sshd_config文件,把参数PasswordAuthentication修改为yes,参数PermitRootLogin修改为yes,同时AllowUsers把root也添加上,重启ssh服务
修改服务器/etc/hosts.allow文件,允许从库连接

1
2
3
4
5
6
7
8
9
10
11
[root@master ~]# sed '/^#/d;/^$/d' /etc/ssh/ssh_config |grep GSSAPIAuthentication
GSSAPIAuthentication no
[root@master ~]# sed '/^#/d;/^$/d' /etc/ssh/sshd_config |grep -E 'PasswordAuthentication|PermitRootLogin'
PermitRootLogin yes
PasswordAuthentication yes
[root@master ~]# /etc/init.d/sshd restart
Stopping sshd: [ OK ]
Starting sshd: [ OK ]
[root@master ~]# cat /etc/hosts.allow |grep 192.168.16.81
sshd: 192.168.16.81
[root@master ~]#

使用命令ssh-keygen生成公钥,发送到从库,同时尝试在主库无密码形式登录从库,而且也要保证本机无密码登录本机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[root@master ~]# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
07:86:76:d6:4a:0f:ea:6e:88:00:eb:3d:5f:7e:08:7a root@master
The key's randomart image is:
+--[ RSA 2048]----+
| |
| . . |
| o B . |
|. . * = |
|.. . S o |
|o o . |
|......o.. |
| ..+.E+. . |
| +o... |
+-----------------+
[root@master ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.16.81
The authenticity of host '192.168.16.81 (192.168.16.81)' can't be established.
RSA key fingerprint is 9f:1f:41:f6:2d:e9:20:83:30:be:cd:20:01:31:ea:6d.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.16.81' (RSA) to the list of known hosts.
root@192.168.16.81's password:
Now try logging into the machine, with "ssh 'root@192.168.16.81'", and check in:

.ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

[root@master ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.16.80
The authenticity of host '192.168.16.80 (192.168.16.80)' can't be established.
RSA key fingerprint is f2:f8:7d:7d:59:3f:b9:3c:a0:e6:66:54:1f:79:e2:40.
Are you sure you want to continue connecting (yes/no)? yes
root@192.168.16.80's password:
Now try logging into the machine, with "ssh 'root@192.168.16.80'", and check in:

.ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

[root@master ~]#

测试登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@master ~]# ssh root@192.168.16.81
Last login: Wed Oct 11 14:45:10 2017 from 192.168.16.80

Welcome to aliyun Elastic Compute Service!

[root@slave ~]# exit
logout
Connection to 192.168.16.81 closed.
[root@master ~]# ssh root@192.168.16.80
Last login: Wed Jan 18 16:25:59 2017

Welcome to aliyun Elastic Compute Service!

[root@master ~]# exit
logout
Connection to 192.168.16.80 closed.
[root@master ~]#

从库设置

和主库保持一致
修改服务器/etc/ssh/ssh_config文件,把参数GSSAPIAuthentication修改为no
修改服务器/etc/ssh/sshd_config文件,把参数PasswordAuthentication修改为yes,参数PermitRootLogin修改为yes,同时AllowUsers把root也添加上,重启ssh服务
修改服务器/etc/hosts.allow文件,允许从库连接

1
2
3
4
5
6
7
8
9
10
11
[root@slave ~]# sed '/^#/d;/^$/d' /etc/ssh/ssh_config |grep GSSAPIAuthentication
GSSAPIAuthentication no
[root@slave ~]# sed '/^#/d;/^$/d' /etc/ssh/sshd_config |grep -E 'PasswordAuthentication|PermitRootLogin'
PermitRootLogin yes
PasswordAuthentication yes
[root@slave ~]# /etc/init.d/sshd restart
Stopping sshd: [ OK ]
Starting sshd: [ OK ]
[root@slave ~]# cat /etc/hosts.allow |grep 192.168.16.80
sshd: 192.168.16.80
[root@slave ~]#

使用命令ssh-keygen生成公钥,发送到从库,同时尝试在主库无密码形式登录从库,而且也要保证本机无密码登录本机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[root@slave ~]# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
ae:04:32:4d:3e:d3:b7:5d:4e:d2:50:7a:4c:44:d7:0a root@slave
The key's randomart image is:
+--[ RSA 2048]----+
| o= .. |
| =E. .|
| . o o. . |
| + . + . |
| o * . S . + |
| o + o o = |
| . o . . |
| . . |
| . |
+-----------------+
[root@slave ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.16.80
The authenticity of host '192.168.16.80 (192.168.16.80)' can't be established.
RSA key fingerprint is f2:f8:7d:7d:59:3f:b9:3c:a0:e6:66:54:1f:79:e2:40.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.16.80' (RSA) to the list of known hosts.
root@192.168.16.80's password:
Now try logging into the machine, with "ssh 'root@192.168.16.80'", and check in:

.ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

[root@slave ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.16.81
The authenticity of host '192.168.16.81 (192.168.16.81)' can't be established.
RSA key fingerprint is 9f:1f:41:f6:2d:e9:20:83:30:be:cd:20:01:31:ea:6d.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.16.81' (RSA) to the list of known hosts.
root@192.168.16.81's password:
Now try logging into the machine, with "ssh 'root@192.168.16.81'", and check in:

.ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

[root@slave ~]#

测试登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@slave ~]# ssh root@192.168.16.80
Last login: Wed Oct 11 14:48:14 2017 from 192.168.16.80

Welcome to aliyun Elastic Compute Service!

[root@master ~]# exit
logout
Connection to 192.168.16.80 closed.
[root@slave ~]# ssh root@192.168.16.81
Last login: Wed Oct 11 14:48:10 2017 from 192.168.16.80

Welcome to aliyun Elastic Compute Service!

[root@slave ~]# exit
logout
Connection to 192.168.16.81 closed.
[root@slave ~]#

MySQL安装

使用的是自己打包的RPM包,分别登录主库和从库,直接rpm -ivh percona-server-5.7.18-15.x86_64.rpm 即可,也可使用其他方式安装MySQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@master ~]# mv /home/duanwenjie/percona-server-5.7.18-15.x86_64.rpm /usr/src/
[root@master ~]# rpm -ivh /usr/src/percona-server-5.7.18-15.x86_64.rpm
Preparing... ########################################### [100%]
Group 'mail' not found. Creating the user mailbox file with 0600 mode.
1:percona-server ########################################### [100%]
error reading information on service /etc/rc.d/init.d/mysqld: No such file or directory
MySQL (Percona Server) PID file could not be found![FAILED]
Starting MySQL (Percona Server)...[ OK ]
mysqladmin: [Warning] Using a password on the command line interface can be insecure.
Warning: Since password will be sent to server in plain text, use ssl connection to ensure password safety.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@master ~]#

查看是否安装成功,分别登录主库和从库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@master ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.18-15-log PLD/Linux Distribution Percona Server RPM

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 15:03:27 > select version();
+---------------+
| version() |
+---------------+
| 5.7.18-15-log |
+---------------+
1 row in set (0.00 sec)

MySQL [(none)] 15:03:31 > exit
Bye
[root@master ~]#

MySQL互为主备

重要参数配置

主库必须包含以下参数

1
2
3
4
5
6
7
8
9
10
[root@master ~]# cat /etc/my.cnf 
[mysqld]
server-id = 1
autocommit = 1
auto_increment_increment = 1
auto_increment_offset = 2
log_bin = mysql-bin
gtid_mode = on
enforce_gtid_consistency = 1
log_slave_updates

从库必须包含以下参数,注意slave需要动态设置read_only=1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@slave ~]# cat /etc/my.cnf 
[mysqld]
server-id = 2
autocommit = 1
auto_increment_increment = 2
auto_increment_offset = 2
log_bin = mysql-bin
gtid_mode = on
enforce_gtid_consistency = 1
log_slave_updates
relay_log_purge=0
[root@slave ~]# mysql -uroot -p -e "show variables like 'read_only'"
Enter password:
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only | OFF |
+---------------+-------+
[root@slave ~]# mysql -uroot -p -e "set global read_only=1"
Enter password:
[root@slave ~]# mysql -uroot -p -e "show variables like 'read_only'"
Enter password:
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| read_only | ON |
+---------------+-------+
[root@slave ~]#

复制帐号建立

由于MySQL版本使用的是5.7,创建用户的方法以之前有些不同

主库创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@master ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.7.18-15-log PLD/Linux Distribution Percona Server RPM

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 15:09:37 > create user 'slave01'@'192.168.16.81' identified by 'slave123456';
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:10:13 > grant replication slave,replication client on *.* to 'slave01'@'192.168.16.81';
Query OK, 0 rows affected (0.03 sec)

MySQL [(none)] 15:10:39 > flush privileges;
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:10:42 > exit
Bye
[root@master ~]#

从库创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@slave ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 5.7.18-15-log PLD/Linux Distribution Percona Server RPM

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 15:11:18 > create user 'slave01'@'192.168.16.80' identified by 'slave123456';
Query OK, 0 rows affected (0.01 sec)

MySQL [(none)] 15:11:30 > grant replication slave,replication client on *.* to 'slave01'@'192.168.16.80';
Query OK, 0 rows affected (0.01 sec)

MySQL [(none)] 15:11:40 > flush privileges;
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:11:43 > exit
Bye
[root@slave ~]#

复制帐号验证

主库登录从库

1
2
3
4
5
6
7
8
[root@master ~]# mysql -uslave01 -pslave123456 -h192.168.16.81 -e "select version()" 
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+
| version() |
+---------------+
| 5.7.18-15-log |
+---------------+
[root@master ~]#

从库登录主库

1
2
3
4
5
6
7
8
[root@slave ~]# mysql -uslave01 -pslave123456 -h192.168.16.80 -e "select version()" 
mysql: [Warning] Using a password on the command line interface can be insecure.
+---------------+
| version() |
+---------------+
| 5.7.18-15-log |
+---------------+
[root@slave ~]#

复制关系配置

主库配置

登录主库,设置复制关系,来源于从库的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
[root@master ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 6
Server version: 5.7.18-15-log PLD/Linux Distribution Percona Server RPM

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 15:14:07 > change master to master_host='192.168.16.81',master_user='slave01',master_password='slave123456',master_auto_position=1;
Query OK, 0 rows affected, 2 warnings (0.03 sec)

MySQL [(none)] 15:14:46 > start slave;
Query OK, 0 rows affected (0.04 sec)

MySQL [(none)] 15:14:49 > show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.16.81
Master_User: slave01
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000002
Read_Master_Log_Pos: 812
Relay_Log_File: master-relay-bin.000002
Relay_Log_Pos: 1025
Relay_Master_Log_File: mysql-bin.000002
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 812
Relay_Log_Space: 1233
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 2
Master_UUID: e1ec82e8-ae51-11e7-8532-00163e128057
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: e1ec82e8-ae51-11e7-8532-00163e128057:1-3
Executed_Gtid_Set: df5051fc-ae51-11e7-85ee-00163e0f0e4a:1-3,
e1ec82e8-ae51-11e7-8532-00163e128057:1-3
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)

MySQL [(none)] 15:14:56 >

从库配置

登录从库,设置复制关系,来源于主库的复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
[root@slave ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 5.7.18-15-log PLD/Linux Distribution Percona Server RPM

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 15:15:42 > change master to master_host='192.168.16.80',master_user='slave01',master_password='slave123456',master_auto_position=1;
Query OK, 0 rows affected, 2 warnings (0.05 sec)

MySQL [(none)] 15:15:53 > start slave;
Query OK, 0 rows affected (0.04 sec)

MySQL [(none)] 15:15:55 > show slave status\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.16.80
Master_User: slave01
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000002
Read_Master_Log_Pos: 1470
Relay_Log_File: slave-relay-bin.000002
Relay_Log_Pos: 1072
Relay_Master_Log_File: mysql-bin.000002
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 1470
Relay_Log_Space: 1279
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: df5051fc-ae51-11e7-85ee-00163e0f0e4a
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set: df5051fc-ae51-11e7-85ee-00163e0f0e4a:1-3
Executed_Gtid_Set: df5051fc-ae51-11e7-85ee-00163e0f0e4a:1-3,
e1ec82e8-ae51-11e7-8532-00163e128057:1-3
Auto_Position: 1
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
1 row in set (0.00 sec)

MySQL [(none)] 15:16:00 >

复制测试

主库登录测试

登录主库,插入测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
MySQL [test11] 15:17:10 > create table if not exists t11(id int unsigned not null auto_increment,name varchar(20),primary key(`id`));
Query OK, 0 rows affected (0.04 sec)

MySQL [test11] 15:17:20 > insert into t11 values(null,'master11');
Query OK, 1 row affected (0.00 sec)

MySQL [test11] 15:17:28 > select * from t11;
+----+----------+
| id | name |
+----+----------+
| 1 | master11 |
+----+----------+
1 row in set (0.00 sec)

登录从库,查看测试数据,此时数据已经复制过来

1
2
3
4
5
6
7
8
[root@slave ~]# mysql -uroot -p test11 -e "select * from t11"
Enter password:
+----+----------+
| id | name |
+----+----------+
| 1 | master11 |
+----+----------+
[root@slave ~]#

从库登录测试

登录从库,插入测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MySQL [(none)] 15:18:56 > create database if not exists test11;use test11;
Query OK, 1 row affected, 1 warning (0.00 sec)

Database changed
MySQL [test11] 15:19:20 > create table if not exists t22(id int unsigned not null auto_increment,name varchar(20),primary key(`id`));
Query OK, 0 rows affected (0.05 sec)

MySQL [test11] 15:19:38 > insert into t22 values(null,'slave11');
Query OK, 1 row affected (0.00 sec)

MySQL [test11] 15:19:43 > select * from t22;
+----+---------+
| id | name |
+----+---------+
| 2 | slave11 |
+----+---------+
1 row in set (0.00 sec)

MySQL [test11] 15:19:51 >

登录主库,查看测试数据,此时数据已经复制过来

1
2
3
4
5
6
7
8
[root@master ~]# mysql -uroot -p test11 -e "select * from t22"
Enter password:
+----+---------+
| id | name |
+----+---------+
| 2 | slave11 |
+----+---------+
[root@master ~]#

MHA帐号创建

主库创建

登录主库,创建允许主库和从库连接的MHA用户信息,再分别尝试使用MHA用户登录(省略)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@master ~]# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 29
Server version: 5.7.18-15-log PLD/Linux Distribution Percona Server RPM

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 15:23:16 > create user 'mha01'@'192.168.16.81' identified by 'mha123456';
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:24:02 > grant all privileges on *.* to 'mha01'@'192.168.16.81';
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:24:27 > create user 'mha01'@'192.168.16.80' identified by 'mha123456';
Query OK, 0 rows affected (0.01 sec)

MySQL [(none)] 15:24:35 > grant all privileges on *.* to 'mha01'@'192.168.16.80';
Query OK, 0 rows affected (0.02 sec)

MySQL [(none)] 15:24:41 > flush privileges;
Query OK, 0 rows affected (0.00 sec)

从库创建

登录从库,由于复制已经把在主库创建的MHA用户信息复制了过来,尝试登录即可(省略)

MHA安装

安装依赖

分别在主库和从库上安装,执行命令:
yum -y install gcc gcc-c++ make openssl-devel perl perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Config-IniFiles perl-Time-HiRes perl-Module-Install.noarch mailx jwhois cpan

主库安装mha4mysql-node

MHA管理端安装在从库上,主库上只需要mha4mysql-node即可,由于MySQL使用5.7版本,MHA也使用最新的0.57版本,采用编译安装方式。下载地址:https://mega.nz/#F!G4oRjARB!SWzFS59bUv9VrKwdAeIGVw

1
2
3
4
5
6
7
8
9
10
11
[root@master ~]# cd /usr/src/
[root@master src]# tar -zxf mha4mysql-node-0.57.tar.gz
[root@master mha4mysql-node-0.57]# perl Makefile.PL #编译过程省略
[root@master mha4mysql-node-0.57]# make
[root@master mha4mysql-node-0.57]# make install
[root@master mha4mysql-node-0.57]# ll /usr/local/bin/ -t #查看安装,有以下文件则mha4mysql-node安装成功
total 1760
-r-xr-xr-x 1 root root 16381 Jul 21 09:41 apply_diff_relay_logs
-r-xr-xr-x 1 root root 8261 Jul 21 09:41 purge_relay_logs
-r-xr-xr-x 1 root root 7525 Jul 21 09:41 save_binary_logs
-r-xr-xr-x 1 root root 4807 Jul 21 09:41 filter_mysqlbinlog

从库安装mha4mysql-manager和mha4mysql-node

MHA管理端安装在从库,从库需要安装manager端和node端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@slave ~]# cd /usr/src/
[root@slave src]# tar -zxf mha4mysql-manager-0.57.tar.gz
[root@slave src]# cd mha4mysql-manager-0.57
[root@slave mha4mysql-manager-0.57]# perl Makefile.PL #编译过程省略
[root@slave mha4mysql-manager-0.57]# make
[root@slave mha4mysql-manager-0.57]# make install
[root@slave mha4mysql-manager-0.57]# ll /usr/local/bin/ -t #查看安装,有以下文件则mha4mysql-manager安装成功
total 40
-r-xr-xr-x 1 root root 1779 Oct 11 15:45 masterha_check_ssh
-r-xr-xr-x 1 root root 2517 Oct 11 15:45 masterha_manager
-r-xr-xr-x 1 root root 2373 Oct 11 15:45 masterha_master_switch
-r-xr-xr-x 1 root root 5171 Oct 11 15:45 masterha_secondary_check
-r-xr-xr-x 1 root root 1995 Oct 11 15:45 masterha_check_repl
-r-xr-xr-x 1 root root 1865 Oct 11 15:45 masterha_check_status
-r-xr-xr-x 1 root root 3201 Oct 11 15:45 masterha_conf_host
-r-xr-xr-x 1 root root 2165 Oct 11 15:45 masterha_master_monitor
-r-xr-xr-x 1 root root 1739 Oct 11 15:45 masterha_stop
[root@slave mha4mysql-manager-0.57]# cd ..
[root@slave src]# tar -zxf mha4mysql-node-0.57.tar.gz
[root@slave src]# cd mha4mysql-node-0.57
[root@slave mha4mysql-node-0.57]# perl Makefile.PL #编译过程省略
[root@slave mha4mysql-node-0.57]# make
[root@slave mha4mysql-node-0.57]# make install
[root@slave mha4mysql-node-0.57]# ll /usr/local/bin/ -t #查看安装,有以下文件则mha4mysql-node安装成功
total 84
-r-xr-xr-x 1 root root 16381 Oct 11 15:47 apply_diff_relay_logs
-r-xr-xr-x 1 root root 4807 Oct 11 15:47 filter_mysqlbinlog
-r-xr-xr-x 1 root root 8261 Oct 11 15:47 purge_relay_logs
-r-xr-xr-x 1 root root 7525 Oct 11 15:47 save_binary_logs

MHA目录结构说明

MHAManager

mhamanager工具包主要包括以下工具

1
2
3
4
5
6
7
8
9
10
11
[root@slave  ~]# ll /usr/local/bin/
总用量 40
-r-xr-xr-x 1 root root 1995 Oct 11 15:45 masterha_check_repl #检查MySQL复制情况
-r-xr-xr-x 1 root root 1779 Oct 11 15:45 masterha_check_ssh #检查MHA的SSH配置情况
-r-xr-xr-x 1 root root 1865 Oct 11 15:45 masterha_check_status #检测当前MHA运行状态
-r-xr-xr-x 1 root root 3201 Oct 11 15:45 masterha_conf_host #添加或删除配置的server信息
-r-xr-xr-x 1 root root 2517 Oct 11 15:45 masterha_manager #启动MHA
-r-xr-xr-x 1 root root 2165 Oct 11 15:45 masterha_master_monitor #检测Master是否宕机
-r-xr-xr-x 1 root root 2373 Oct 11 15:45 masterha_master_switch #控制故障转移,自动或者手动
-r-xr-xr-x 1 root root 5172 Oct 11 15:45 masterha_secondary_check #通过其他路由检测Master是否真的宕机
-r-xr-xr-x 1 root root 1739 Oct 11 15:45 masterha_stop #停止MHA

MHANode

mhanode工具包主要包括以下工具

1
2
3
4
5
6
[root@slave ~]# ll /usr/local/bin/ -t   
total 84
-r-xr-xr-x 1 root root 16371 Oct 11 15:47 apply_diff_relay_logs #识别差异日志的中继日志,并将其差异事件应用于其他Slave
-r-xr-xr-x 1 root root 4807 Oct 11 15:47 filter_mysqlbinlog #去除不必要的Rollback事件
-r-xr-xr-x 1 root root 8263 Oct 11 15:47 purge_relay_logs #删除无用的Relay log,避免延时
-r-xr-xr-x 1 root root 7525 Oct 11 15:47 save_binary_logs #保存和复制down掉的主服务器二进制日志

自定义扩展脚本说明

secondary_check_script #通过多条网络路由检测master的可用性
master_ip_failover_script #自动failover时候的切换脚本,可将vip信息写入此脚本中
shutdown_script #强制关闭master节点执行脚本
report_script #发送报告
init_conf_load_script #加载初始配置参数,如不想在配置中写明文密码
master_ip_online_change_script #手动failover时候的切换脚本

keepalived安装

MHA作为MySQL的HA软件,借助脚本或者第三方软件如keepalived,可实现自动的failover
本次环境使用yum安装keepalived,主库和从库分别执行安装: yum -y install keepalived
keepalived的配置文件,默认在主库上绑定EIP,priority要比备库高,同时为了避免脑裂,两个state同时设置为BACKUP,由于HaVip不支持组播和广播通讯,因此需要将keepalived的心跳方式设置为单播,添加unicast_src_ip和unicast_peer

配置文件

主库配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@master ~]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
notification_email {
duanwenjie@huan.tv
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LVS_DEVEL
}

vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.16.82 dev eth0 label eth0:havip
}
unicast_src_ip 192.168.16.80
unicast_peer {
192.168.16.81
}
}

从库配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@slave ~]# cat /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
notification_email {
duanwenjie@huan.tv
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id LVS_DEVEL
}

vrrp_instance VI_1 {
state BACKUP
interface eth0
virtual_router_id 51
priority 99
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.16.82 dev eth0 label eth0:havip
}
unicast_src_ip 192.168.16.81
unicast_peer {
192.168.16.80
}
}

EIP漂移测试

分别启动keepalived服务,查看默认EIP在哪台机器上,同时关闭EIP所在机器的keepalived服务,查看EIP是否漂移,最后恢复原状

主库启动keepalived服务

1
2
3
[root@master keepalived]# /etc/init.d/keepalived start
Starting keepalived: [ OK ]
[root@master keepalived]#

从库启动keepalived服务

1
2
3
[root@slave keepalived]# /etc/init.d/keepalived start
Starting keepalived: [ OK ]
[root@slave keepalived]#

查看默认EIP在主库上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@master ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.80 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:172562 errors:0 dropped:0 overruns:0 frame:0
TX packets:71674 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:200695958 (191.3 MiB) TX bytes:12526649 (11.9 MiB)

eth0:havip Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.82 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:7946 errors:0 dropped:0 overruns:0 frame:0
TX packets:7946 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:510910 (498.9 KiB) TX bytes:510910 (498.9 KiB)

[root@master ~]#

关闭主库的keepalived服务

关闭主库的keepalived服务后,EIP消失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# /etc/init.d/keepalived stop
Stopping keepalived: [ OK ]
[root@master ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.80 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:172670 errors:0 dropped:0 overruns:0 frame:0
TX packets:71803 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:200704346 (191.4 MiB) TX bytes:12569281 (11.9 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:7976 errors:0 dropped:0 overruns:0 frame:0
TX packets:7976 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:512770 (500.7 KiB) TX bytes:512770 (500.7 KiB)

[root@master ~]#

查看EIP是否漂移到从库上

此时EIP已经漂移到从库上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@slave ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:16:3E:12:80:57
inet addr:192.168.16.81 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:164539 errors:0 dropped:0 overruns:0 frame:0
TX packets:77532 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:202145589 (192.7 MiB) TX bytes:12091265 (11.5 MiB)

eth0:havip Link encap:Ethernet HWaddr 00:16:3E:12:80:57
inet addr:192.168.16.82 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:7961 errors:0 dropped:0 overruns:0 frame:0
TX packets:7961 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:508050 (496.1 KiB) TX bytes:508050 (496.1 KiB)

[root@slave ~]#

恢复原状

主库启动keepalived服务,查看EIP已经又漂移回来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@master ~]# /etc/init.d/keepalived start
Starting keepalived: [ OK ]
[root@master ~]# ifconfig
eth0 Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.80 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:172863 errors:0 dropped:0 overruns:0 frame:0
TX packets:71978 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:200717148 (191.4 MiB) TX bytes:12627987 (12.0 MiB)

eth0:havip Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.82 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:8039 errors:0 dropped:0 overruns:0 frame:0
TX packets:8039 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:516676 (504.5 KiB) TX bytes:516676 (504.5 KiB)

[root@master ~]#

MHA配置

MHAManager

创建配置目录/etc/masterha/,同时创建一个项目上的配置文件,仅配置自动FailOver部分,脚本暂时不定义,下文会有MHA引入keepalived。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[server default]
manager_workdir=/var/log/masterha/app1
manager_log=/var/log/masterha/app1/manager.log
master_binlog_dir=/hwdata/data/percona
password=mha123456
user=mha01
ping_interval=1
remote_workdir=/tmp
repl_password=slave123456
repl_user=slave01
ssh_user=root
#master_ip_failover_script=/usr/local/bin/master_ip_failover
#master_ip_online_change_script= /usr/local/bin/master_ip_online_change
#report_script=/usr/local/bin/send_report
#shutdown_script=
secondary_check_script = masterha_secondary_check -s 192.168.16.80 -s 192.168.16.81 --user=root hostname=192.168.16.80 --master_ip=192.168.16.80 --master_port=3306
[server1]
hostname=192.168.16.80
port=3306
[server2]
hostname=192.168.16.81
port=3306
candidate_master=1

测试MHAManager SSH

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@slave masterha]# masterha_check_ssh --conf=/etc/masterha/app1.cnf 
Wed Oct 11 17:21:46 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Oct 11 17:21:46 2017 - [info] Reading application default configuration from /etc/masterha/app1.cnf..
Wed Oct 11 17:21:46 2017 - [info] Reading server configuration from /etc/masterha/app1.cnf..
Wed Oct 11 17:21:46 2017 - [info] Starting SSH connection tests..
Wed Oct 11 17:21:47 2017 - [debug]
Wed Oct 11 17:21:46 2017 - [debug] Connecting via SSH from root@192.168.16.80(192.168.16.80:22) to root@192.168.16.81(192.168.16.81:22)..
Wed Oct 11 17:21:47 2017 - [debug] ok.
Wed Oct 11 17:21:47 2017 - [debug]
Wed Oct 11 17:21:47 2017 - [debug] Connecting via SSH from root@192.168.16.81(192.168.16.81:22) to root@192.168.16.80(192.168.16.80:22)..
Wed Oct 11 17:21:47 2017 - [debug] ok.
Wed Oct 11 17:21:47 2017 - [info] All SSH connection tests passed successfully.
[root@slave masterha]#

测试MHAManager 复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@slave masterha]# masterha_check_repl --conf=/etc/masterha/app1.cnf
Wed Oct 11 17:22:47 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Wed Oct 11 17:22:47 2017 - [info] Reading application default configuration from /etc/masterha/app1.cnf..
Wed Oct 11 17:22:47 2017 - [info] Reading server configuration from /etc/masterha/app1.cnf..
Wed Oct 11 17:22:47 2017 - [info] MHA::MasterMonitor version 0.57.
Creating directory /var/log/masterha/app1.. done.
Wed Oct 11 17:22:48 2017 - [info] Multi-master configuration is detected. Current primary(writable) master is 192.168.16.80(192.168.16.80:3306)
Wed Oct 11 17:22:48 2017 - [info] Master configurations are as below:
Master 192.168.16.81(192.168.16.81:3306), replicating from 192.168.16.80(192.168.16.80:3306), read-only
Master 192.168.16.80(192.168.16.80:3306), replicating from 192.168.16.81(192.168.16.81:3306)

Wed Oct 11 17:22:48 2017 - [info] GTID failover mode = 1
Wed Oct 11 17:22:48 2017 - [info] Dead Servers:
Wed Oct 11 17:22:48 2017 - [info] Alive Servers:
Wed Oct 11 17:22:48 2017 - [info] 192.168.16.80(192.168.16.80:3306)
Wed Oct 11 17:22:48 2017 - [info] 192.168.16.81(192.168.16.81:3306)
Wed Oct 11 17:22:48 2017 - [info] Alive Slaves:
Wed Oct 11 17:22:48 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Wed Oct 11 17:22:48 2017 - [info] GTID ON
Wed Oct 11 17:22:48 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Wed Oct 11 17:22:48 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Wed Oct 11 17:22:48 2017 - [info] Current Alive Master: 192.168.16.80(192.168.16.80:3306)
Wed Oct 11 17:22:48 2017 - [info] Checking slave configurations..
Wed Oct 11 17:22:48 2017 - [info] Checking replication filtering settings..
Wed Oct 11 17:22:48 2017 - [info] binlog_do_db= , binlog_ignore_db=
Wed Oct 11 17:22:48 2017 - [info] Replication filtering check ok.
Wed Oct 11 17:22:48 2017 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Wed Oct 11 17:22:48 2017 - [info] Checking SSH publickey authentication settings on the current master..
Wed Oct 11 17:22:48 2017 - [info] HealthCheck: SSH to 192.168.16.80 is reachable.
Wed Oct 11 17:22:48 2017 - [info]
192.168.16.80(192.168.16.80:3306) (current master)
+--192.168.16.81(192.168.16.81:3306)

Wed Oct 11 17:22:48 2017 - [info] Checking replication health on 192.168.16.81..
Wed Oct 11 17:22:48 2017 - [info] ok.
Wed Oct 11 17:22:48 2017 - [warning] master_ip_failover_script is not defined.
Wed Oct 11 17:22:48 2017 - [warning] shutdown_script is not defined.
Wed Oct 11 17:22:48 2017 - [info] Got exit code 0 (Not master dead).

MySQL Replication Health is OK.
[root@slave masterha]#

MHA引入keepalived

把keepalived服务引入MHA,需要修改切换是触发的脚本文件master_ip_failover即可,在该脚本中添加在master发生宕机时对keepalived的处理。

master_ip_failover脚本

编辑脚本/usr/local/bin/master_ip_failover,修改后如下,这里完整的脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
[root@slave app1]# cat /usr/local/bin/master_ip_failover 
#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';

use Getopt::Long;

my (
$command, $ssh_user, $orig_master_host, $orig_master_ip,
$orig_master_port, $new_master_host, $new_master_ip, $new_master_port
);

my $vip = '192.168.16.82';
my $ssh_start_vip = "/etc/init.d/keepalived start";
my $ssh_stop_vip = "/etc/init.d/keepalived stop";

GetOptions(
'command=s' => \$command,
'ssh_user=s' => \$ssh_user,
'orig_master_host=s' => \$orig_master_host,
'orig_master_ip=s' => \$orig_master_ip,
'orig_master_port=i' => \$orig_master_port,
'new_master_host=s' => \$new_master_host,
'new_master_ip=s' => \$new_master_ip,
'new_master_port=i' => \$new_master_port,
);

exit &main();

sub main {

print "\n\nIN SCRIPT TEST====$ssh_stop_vip==$ssh_start_vip===\n\n";

if ( $command eq "stop" || $command eq "stopssh" ) {

my $exit_code = 1;
eval {
print "Disabling the VIP on old master: $orig_master_host \n";
&stop_vip();
$exit_code = 0;
};
if ($@) {
warn "Got Error: $@\n";
exit $exit_code;
}
exit $exit_code;
}
elsif ( $command eq "start" ) {

my $exit_code = 10;
eval {
print "Enabling the VIP - $vip on the new master - $new_master_host \n";
&start_vip();
$exit_code = 0;
};
if ($@) {
warn $@;
exit $exit_code;
}
exit $exit_code;
}
elsif ( $command eq "status" ) {
print "Checking the Status of the script.. OK \n";
#`ssh $ssh_user\@$orig_master_ip \" $ssh_start_vip \"`;
exit 0;
}
else {
&usage();
exit 1;
}
}

# A simple system call that enable the VIP on the new master
sub start_vip() {
`ssh $ssh_user\@$new_master_host \" $ssh_start_vip \"`;
}
# A simple system call that disable the VIP on the old_master
sub stop_vip() {
return 0 unless ($ssh_user);
`ssh $ssh_user\@$orig_master_host \" $ssh_stop_vip \"`;
}

sub usage {
print
"Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port\n";
}

[root@slave app1]#

添加权限
chmod +x /usr/local/bin/master_ip_failover

send_report脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
[root@slave masterha]# cat /usr/local/bin/send_report 
#!/usr/bin/perl

use strict;
use warnings FATAL => 'all';
use Mail::Sender;
use Getopt::Long;

#new_master_host and new_slave_hosts are set only when recovering master succeeded
my ( $dead_master_host, $new_master_host, $new_slave_hosts, $subject, $body );
my $smtp='smtp.126.com';
my $mail_from='xxx@126.com';
my $mail_user='xxx@126.com';
my $mail_pass='xxx';
#my $mail_to=['xxx','xxx'];
my $mail_to='xxx';
GetOptions(
'orig_master_host=s' => \$dead_master_host,
'new_master_host=s' => \$new_master_host,
'new_slave_hosts=s' => \$new_slave_hosts,
'subject=s' => \$subject,
'body=s' => \$body,
);

mailToContacts($smtp,$mail_from,$mail_user,$mail_pass,$mail_to,$subject,$body);

sub mailToContacts {
my ( $smtp, $mail_from, $user, $passwd, $mail_to, $subject, $msg ) = @_;
open my $DEBUG, "> /var/log/masterha/app1/manager.log"
or die "Can't open the debug file:$!\n";
my $sender = new Mail::Sender {
ctype => 'text/plain; charset=utf-8',
encoding => 'utf-8',
smtp => $smtp,
from => $mail_from,
auth => 'LOGIN',
TLS_allowed => '0',
authid => $user,
authpwd => $passwd,
to => $mail_to,
subject => $subject,
debug => $DEBUG
};

$sender->MailMsg(
{ msg => $msg,
debug => $DEBUG
}
) or print $Mail::Sender::Error;
return 1;
}

# Do whatever you want here

exit 0;
[root@slave masterha]#

添加权限
chmod +x /usr/local/bin/send_report

MHAManager修改注释

1
2
3
4
5
[root@slave app1]# cat /etc/masterha/app1.cnf |grep master_ip_failover_script
master_ip_failover_script=/usr/local/bin/master_ip_failover
[root@slave app1]# cat /etc/masterha/app1.cnf |grep report
report_script=/usr/local/bin/send_report
[root@slave app1]#

HaVip绑定EIP

keepalived服务已经配置完成,VIP也可以自动漂移后,但是其还不能在内网中通讯,需要将EIP映射到HaVip中,创建高可用虚拟IP,把master和slave两个实例的绑定在一起,具体步骤省略,见下图
image

MHA启动

1
2
3
[root@slave masterha]# nohup masterha_manager --conf=/etc/masterha/app1.cnf &
[1] 6706
[root@slave masterha]# nohup: ignoring input and appending output to `nohup.out'

查看启动日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@slave app1]# cat /var/log/masterha/app1/manager.log
Thu Oct 12 09:26:56 2017 - [info] MHA::MasterMonitor version 0.57.
Thu Oct 12 09:26:57 2017 - [info] Multi-master configuration is detected. Current primary(writable) master is 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:26:57 2017 - [info] Master configurations are as below:
Master 192.168.16.81(192.168.16.81:3306), replicating from 192.168.16.80(192.168.16.80:3306), read-only
Master 192.168.16.80(192.168.16.80:3306), replicating from 192.168.16.81(192.168.16.81:3306)

Thu Oct 12 09:26:57 2017 - [info] GTID failover mode = 1
Thu Oct 12 09:26:57 2017 - [info] Dead Servers:
Thu Oct 12 09:26:57 2017 - [info] Alive Servers:
Thu Oct 12 09:26:57 2017 - [info] 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:26:57 2017 - [info] 192.168.16.81(192.168.16.81:3306)
Thu Oct 12 09:26:57 2017 - [info] Alive Slaves:
Thu Oct 12 09:26:57 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Thu Oct 12 09:26:57 2017 - [info] GTID ON
Thu Oct 12 09:26:57 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:26:57 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Thu Oct 12 09:26:57 2017 - [info] Current Alive Master: 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:26:57 2017 - [info] Checking slave configurations..
Thu Oct 12 09:26:57 2017 - [info] Checking replication filtering settings..
Thu Oct 12 09:26:57 2017 - [info] binlog_do_db= , binlog_ignore_db=
Thu Oct 12 09:26:57 2017 - [info] Replication filtering check ok.
Thu Oct 12 09:26:57 2017 - [info] GTID (with auto-pos) is supported. Skipping all SSH and Node package checking.
Thu Oct 12 09:26:57 2017 - [info] Checking SSH publickey authentication settings on the current master..
Thu Oct 12 09:26:57 2017 - [info] HealthCheck: SSH to 192.168.16.80 is reachable.
Thu Oct 12 09:26:57 2017 - [info]
192.168.16.80(192.168.16.80:3306) (current master)
+--192.168.16.81(192.168.16.81:3306)

Thu Oct 12 09:26:57 2017 - [info] Checking master_ip_failover_script status:
Thu Oct 12 09:26:57 2017 - [info] /usr/local/bin/master_ip_failover --command=status --ssh_user=root --orig_master_host=192.168.16.80 --orig_master_ip=192.168.16.80 --orig_master_port=3306


IN SCRIPT TEST====/etc/init.d/keepalived stop==/etc/init.d/keepalived start===

Checking the Status of the script.. OK
Thu Oct 12 09:26:57 2017 - [info] OK.
Thu Oct 12 09:26:57 2017 - [warning] shutdown_script is not defined.
Thu Oct 12 09:26:57 2017 - [info] Set master ping interval 1 seconds.
Thu Oct 12 09:26:57 2017 - [info] Set secondary check script: masterha_secondary_check -s 192.168.16.80 -s 192.168.16.81 --user=root hostname=192.168.16.80 --master_ip=192.168.16.80 --master_port=3306
Thu Oct 12 09:26:57 2017 - [info] Starting ping health check on 192.168.16.80(192.168.16.80:3306)..
Thu Oct 12 09:26:57 2017 - [info] Ping(SELECT) succeeded, waiting until MySQL doesn't respond..
[root@slave app1]#

模拟主库故障(自动FailOver)

确认MHAManager进程状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@slave app1]# ps aux|grep master
root 1217 0.0 0.0 81520 3440 ? Ss Oct11 0:00 /usr/libexec/postfix/master
root 20887 0.2 0.4 194688 18736 pts/1 S 08:57 0:00 perl /usr/local/bin/masterha_manager --conf=/etc/masterha/app1.cnf
root 20998 0.0 0.0 103252 844 pts/1 S+ 08:59 0:00 grep master
[root@slave app1]# ifconfig #VIP不在slave上
eth0 Link encap:Ethernet HWaddr 00:16:3E:12:80:57
inet addr:192.168.16.81 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:288188 errors:0 dropped:0 overruns:0 frame:0
TX packets:129868 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:211584573 (201.7 MiB) TX bytes:50227849 (47.9 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:50346 errors:0 dropped:0 overruns:0 frame:0
TX packets:50346 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3204922 (3.0 MiB) TX bytes:3204922 (3.0 MiB)

[root@slave app1]#

停止主库MySQL服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@master ~]# ifconfig   #停止前VIP存在于主库上
eth0 Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.80 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:278061 errors:0 dropped:0 overruns:0 frame:0
TX packets:223278 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:210643899 (200.8 MiB) TX bytes:57552395 (54.8 MiB)

eth0:havip Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.82 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:50158 errors:0 dropped:0 overruns:0 frame:0
TX packets:50158 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3130070 (2.9 MiB) TX bytes:3130070 (2.9 MiB)

[root@master ~]# /etc/init.d/mysqld stop
Shutting down MySQL (Percona Server)............ [ OK ]
[root@master ~]# ifconfig #停止后VIP已经漂移走
eth0 Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.80 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:278240 errors:0 dropped:0 overruns:0 frame:0
TX packets:223445 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:210664827 (200.9 MiB) TX bytes:57596785 (54.9 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:50175 errors:0 dropped:0 overruns:0 frame:0
TX packets:50175 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3131100 (2.9 MiB) TX bytes:3131100 (2.9 MiB)

[root@master ~]#

查看FailOver日志

登录从库,查看MHAManager日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
[root@slave app1]# tail -f /var/log/masterha/app1/manager.log

IN SCRIPT TEST====/etc/init.d/keepalived stop==/etc/init.d/keepalived start===

Checking the Status of the script.. OK
Thu Oct 12 09:26:57 2017 - [info] OK.
Thu Oct 12 09:26:57 2017 - [warning] shutdown_script is not defined.
Thu Oct 12 09:26:57 2017 - [info] Set master ping interval 1 seconds.
Thu Oct 12 09:26:57 2017 - [info] Set secondary check script: masterha_secondary_check -s 192.168.16.80 -s 192.168.16.81 --user=root hostname=192.168.16.80 --master_ip=192.168.16.80 --master_port=3306
Thu Oct 12 09:26:57 2017 - [info] Starting ping health check on 192.168.16.80(192.168.16.80:3306)..
Thu Oct 12 09:26:57 2017 - [info] Ping(SELECT) succeeded, waiting until MySQL doesn't respond.. #开始监听服务状态
#监控到master故障后,MHA开始做自动FailOver
Thu Oct 12 09:27:54 2017 - [warning] Got error on MySQL select ping: 2006 (MySQL server has gone away)
Thu Oct 12 09:27:54 2017 - [info] Executing secondary network check script: masterha_secondary_check -s 192.168.16.80 -s 192.168.16.81 --user=root hostname=192.168.16.80 --master_ip=192.168.16.80 --master_port=3306 --user=root --master_host=192.168.16.80 --master_ip=192.168.16.80 --master_port=3306 --master_user=mha01 --master_password=mha123456 --ping_type=SELECT
Thu Oct 12 09:27:54 2017 - [info] Executing SSH check script: exit 0
Thu Oct 12 09:27:54 2017 - [info] HealthCheck: SSH to 192.168.16.80 is reachable.
Monitoring server 192.168.16.80 is reachable, Master is not reachable from 192.168.16.80. OK.
Monitoring server 192.168.16.81 is reachable, Master is not reachable from 192.168.16.81. OK.
Thu Oct 12 09:27:54 2017 - [info] Master is not reachable from all other monitoring servers. Failover should start.
Thu Oct 12 09:27:55 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Thu Oct 12 09:27:55 2017 - [warning] Connection failed 2 time(s)..
Thu Oct 12 09:27:56 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Thu Oct 12 09:27:56 2017 - [warning] Connection failed 3 time(s)..
Thu Oct 12 09:27:57 2017 - [warning] Got error on MySQL connect: 2013 (Lost connection to MySQL server at 'reading initial communication packet', system error: 111)
Thu Oct 12 09:27:57 2017 - [warning] Connection failed 4 time(s)..
Thu Oct 12 09:27:57 2017 - [warning] Master is not reachable from health checker!
Thu Oct 12 09:27:57 2017 - [warning] Master 192.168.16.80(192.168.16.80:3306) is not reachable!
Thu Oct 12 09:27:57 2017 - [warning] SSH is reachable.
Thu Oct 12 09:27:57 2017 - [info] Connecting to a master server failed. Reading configuration file /etc/masterha_default.cnf and /etc/masterha/app1.cnf again, and trying to connect to all servers to check server status..
Thu Oct 12 09:27:57 2017 - [warning] Global configuration file /etc/masterha_default.cnf not found. Skipping.
Thu Oct 12 09:27:57 2017 - [info] Reading application default configuration from /etc/masterha/app1.cnf..
Thu Oct 12 09:27:57 2017 - [info] Reading server configuration from /etc/masterha/app1.cnf..
Thu Oct 12 09:27:58 2017 - [info] GTID failover mode = 1
Thu Oct 12 09:27:58 2017 - [info] Dead Servers:
Thu Oct 12 09:27:58 2017 - [info] 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:58 2017 - [info] Alive Servers:
Thu Oct 12 09:27:58 2017 - [info] 192.168.16.81(192.168.16.81:3306)
Thu Oct 12 09:27:58 2017 - [info] Alive Slaves:
Thu Oct 12 09:27:58 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Thu Oct 12 09:27:58 2017 - [info] GTID ON
Thu Oct 12 09:27:58 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:58 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Thu Oct 12 09:27:58 2017 - [info] Checking slave configurations..
Thu Oct 12 09:27:58 2017 - [info] Checking replication filtering settings..
Thu Oct 12 09:27:58 2017 - [info] Replication filtering check ok.
Thu Oct 12 09:27:58 2017 - [info] Master is down!
Thu Oct 12 09:27:58 2017 - [info] Terminating monitoring script.
Thu Oct 12 09:27:58 2017 - [info] Got exit code 20 (Master dead).
Thu Oct 12 09:27:58 2017 - [info] MHA::MasterFailover version 0.57.
Thu Oct 12 09:27:58 2017 - [info] Starting master failover.
Thu Oct 12 09:27:58 2017 - [info]
Thu Oct 12 09:27:58 2017 - [info] * Phase 1: Configuration Check Phase..
Thu Oct 12 09:27:58 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] GTID failover mode = 1
Thu Oct 12 09:27:59 2017 - [info] Dead Servers:
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:59 2017 - [info] Checking master reachability via MySQL(double check)...
Thu Oct 12 09:27:59 2017 - [info] ok.
Thu Oct 12 09:27:59 2017 - [info] Alive Servers:
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.81(192.168.16.81:3306)
Thu Oct 12 09:27:59 2017 - [info] Alive Slaves:
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Thu Oct 12 09:27:59 2017 - [info] GTID ON
Thu Oct 12 09:27:59 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:59 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Thu Oct 12 09:27:59 2017 - [info] Starting GTID based failover.
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] ** Phase 1: Configuration Check Phase completed.
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 2: Dead Master Shutdown Phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] Forcing shutdown so that applications never connect to the current master..
Thu Oct 12 09:27:59 2017 - [info] Executing master IP deactivation script:
Thu Oct 12 09:27:59 2017 - [info] /usr/local/bin/master_ip_failover --orig_master_host=192.168.16.80 --orig_master_ip=192.168.16.80 --orig_master_port=3306 --command=stopssh --ssh_user=root


IN SCRIPT TEST====/etc/init.d/keepalived stop==/etc/init.d/keepalived start===

Disabling the VIP on old master: 192.168.16.80
Thu Oct 12 09:27:59 2017 - [info] done.
Thu Oct 12 09:27:59 2017 - [warning] shutdown_script is not set. Skipping explicit shutting down of the dead master.
Thu Oct 12 09:27:59 2017 - [info] * Phase 2: Dead Master Shutdown Phase completed.
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 3: Master Recovery Phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 3.1: Getting Latest Slaves Phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] The latest binary log file/position on all slaves is mysql-bin.000005:234
Thu Oct 12 09:27:59 2017 - [info] Latest slaves (Slaves that received relay log files to the latest):
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Thu Oct 12 09:27:59 2017 - [info] GTID ON
Thu Oct 12 09:27:59 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:59 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Thu Oct 12 09:27:59 2017 - [info] The oldest binary log file/position on all slaves is mysql-bin.000005:234
Thu Oct 12 09:27:59 2017 - [info] Oldest slaves:
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Thu Oct 12 09:27:59 2017 - [info] GTID ON
Thu Oct 12 09:27:59 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:59 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 3.3: Determining New Master Phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] Searching new master from slaves..
Thu Oct 12 09:27:59 2017 - [info] Candidate masters from the configuration file:
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.81(192.168.16.81:3306) Version=5.7.18-15-log (oldest major version between slaves) log-bin:enabled
Thu Oct 12 09:27:59 2017 - [info] GTID ON
Thu Oct 12 09:27:59 2017 - [info] Replicating from 192.168.16.80(192.168.16.80:3306)
Thu Oct 12 09:27:59 2017 - [info] Primary candidate for the new Master (candidate_master is set)
Thu Oct 12 09:27:59 2017 - [info] Non-candidate masters:
Thu Oct 12 09:27:59 2017 - [info] Searching from candidate_master slaves which have received the latest relay log events..
Thu Oct 12 09:27:59 2017 - [info] New master is 192.168.16.81(192.168.16.81:3306)
Thu Oct 12 09:27:59 2017 - [info] Starting master failover..
Thu Oct 12 09:27:59 2017 - [info]
From:
192.168.16.80(192.168.16.80:3306) (current master)
+--192.168.16.81(192.168.16.81:3306)

To:
192.168.16.81(192.168.16.81:3306) (new master)
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 3.3: New Master Recovery Phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] Waiting all logs to be applied..
Thu Oct 12 09:27:59 2017 - [info] done.
Thu Oct 12 09:27:59 2017 - [info] Getting new master's binlog name and position..
Thu Oct 12 09:27:59 2017 - [info] mysql-bin.000002:4876
Thu Oct 12 09:27:59 2017 - [info] All other slaves should start replication from here. Statement should be: CHANGE MASTER TO MASTER_HOST='192.168.16.81', MASTER_PORT=3306, MASTER_AUTO_POSITION=1, MASTER_USER='slave01', MASTER_PASSWORD='xxx';
Thu Oct 12 09:27:59 2017 - [info] Master Recovery succeeded. File:Pos:Exec_Gtid_Set: mysql-bin.000002, 4876, df5051fc-ae51-11e7-85ee-00163e0f0e4a:1-15,
e1ec82e8-ae51-11e7-8532-00163e128057:1-6
Thu Oct 12 09:27:59 2017 - [info] Executing master IP activate script:
Thu Oct 12 09:27:59 2017 - [info] /usr/local/bin/master_ip_failover --command=start --ssh_user=root --orig_master_host=192.168.16.80 --orig_master_ip=192.168.16.80 --orig_master_port=3306 --new_master_host=192.168.16.81 --new_master_ip=192.168.16.81 --new_master_port=3306 --new_master_user='mha01' --new_master_password=xxx
Unknown option: new_master_user
Unknown option: new_master_password


IN SCRIPT TEST====/etc/init.d/keepalived stop==/etc/init.d/keepalived start===

Enabling the VIP - 192.168.16.200 on the new master - 192.168.16.81
Thu Oct 12 09:27:59 2017 - [info] OK.
Thu Oct 12 09:27:59 2017 - [info] Setting read_only=0 on 192.168.16.81(192.168.16.81:3306)..
Thu Oct 12 09:27:59 2017 - [info] ok.
Thu Oct 12 09:27:59 2017 - [info] ** Finished master recovery successfully.
Thu Oct 12 09:27:59 2017 - [info] * Phase 3: Master Recovery Phase completed.
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 4: Slaves Recovery Phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 4.1: Starting Slaves in parallel..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] All new slave servers recovered successfully.
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] * Phase 5: New master cleanup phase..
Thu Oct 12 09:27:59 2017 - [info]
Thu Oct 12 09:27:59 2017 - [info] Resetting slave info on the new master..
Thu Oct 12 09:27:59 2017 - [info] 192.168.16.81: Resetting slave info succeeded.
Thu Oct 12 09:27:59 2017 - [info] Master failover to 192.168.16.81(192.168.16.81:3306) completed successfully.
Thu Oct 12 09:27:59 2017 - [info]

----- Failover Report -----

app1: MySQL Master failover 192.168.16.80(192.168.16.80:3306) to 192.168.16.81(192.168.16.81:3306) succeeded

Master 192.168.16.80(192.168.16.80:3306) is down!

Check MHA Manager logs at slave:/var/log/masterha/app1/manager.log for details.

Started automated(non-interactive) failover.
Invalidated master IP address on 192.168.16.80(192.168.16.80:3306)
Selected 192.168.16.81(192.168.16.81:3306) as a new master.
192.168.16.81(192.168.16.81:3306): OK: Applying all logs succeeded.
192.168.16.81(192.168.16.81:3306): OK: Activated master IP address.
192.168.16.81(192.168.16.81:3306): Resetting slave info succeeded.
Master failover to 192.168.16.81(192.168.16.81:3306) completed successfully.
Thu Oct 12 09:27:59 2017 - [info] Sending mail..
Option new_slave_hosts requires an argument
Unknown option: conf
tail: /var/log/masterha/app1/manager.log: file truncated
^C
[1]+ Done nohup masterha_manager --conf=/etc/masterha/app1.cnf (wd: /etc/masterha)
(wd now: /var/log/masterha/app1)
[root@slave app1]#

注意:
由于阿里云的ECS默认不允许发送邮件,它们把25端口已经封掉,如果需要开通25端口,主联系阿里云技术支持。本次切换日志在最后没有发送邮件,是由于这个原因导致的。

日志解析过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
启动前的准备工作
检查数据库服务器状态,获取相关参数设置
检查GTID、candidate_master、过滤DB是否设置
测试ssh连接是否成功
测试MHA node是否可用
创建MHA日志目录
开始检查slave的差异日志应用权限
确定当前的复制架构
调试master_ip_failover_script
调试shutdown_script
设置二次检查的主机masterha_secondary_check
MHA启动完毕,进入监测状态
监测master服务器挂了
通过定义的二次监测,确认master是否挂了
确认master挂了,开始进入failover流程
再试尝试连接master和master的ssh
通过MHA配置文件,监测其他slave的状态
再次监测slave的配置是否有变化,是否符合failover条件
正式开始failover
再次对slave配置做检查
对原Master做master_ip_failover_script和shutdown_script的操作
开始差异日志的恢复,获取slave最后得到的binlog位置
获取原master的binlog日志
确定新的master
在new master上应用差异的binlog日志
获取new master的binlog位置。
执行master_ip_failover_script,调用aws_vip_change.sh,执行VIP漂移
开始恢复其他slave的差异日志
差异日志应用完成以后,切换所有slave到new master。
failover操作完成,生成failover报告
最后发送邮件通知

确认FailOver状态

登录从库,查看VIP是否漂移过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@slave app1]# ifconfig     #VIP已经漂移过程
eth0 Link encap:Ethernet HWaddr 00:16:3E:12:80:57
inet addr:192.168.16.81 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:295022 errors:0 dropped:0 overruns:0 frame:0
TX packets:135929 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:212253710 (202.4 MiB) TX bytes:52246361 (49.8 MiB)

eth0:havip Link encap:Ethernet HWaddr 00:16:3E:12:80:57
inet addr:192.168.16.82 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:52973 errors:0 dropped:0 overruns:0 frame:0
TX packets:52973 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3597938 (3.4 MiB) TX bytes:3597938 (3.4 MiB)

[root@slave app1]#

登录主库,查看VIP是否存在,keepalived服务状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@master ~]# ifconfig   #VIP已经不存在
eth0 Link encap:Ethernet HWaddr 00:16:3E:0F:0E:4A
inet addr:192.168.16.80 Bcast:192.168.31.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:284277 errors:0 dropped:0 overruns:0 frame:0
TX packets:229786 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:211190989 (201.4 MiB) TX bytes:60010868 (57.2 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:51564 errors:0 dropped:0 overruns:0 frame:0
TX packets:51564 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3217146 (3.0 MiB) TX bytes:3217146 (3.0 MiB)

[root@master ~]# /etc/init.d/keepalived status #keepalived服务已经停止
keepalived is stopped
[root@master ~]#

手动FailOver

MHA的手动FailOver不在本文范围,详情理论可参考官方文档。

参数列表

请参考之前文章。

参考

https://dwj999.github.io/AWS-EC2搭建mha-vip-MySQL5-7.htm

结束语

云服务器上MySQL高可用,也可通过云负载均衡产品+MySQL复制来实现,但在数据安全性上,没有MHA+VIP+MySQL相对安全。

ProxySQL 安装配置详解及读写分离、负载均衡

发表于 2017-08-29 | 分类于 中间件
字数统计: | 阅读时长 ≈

前言

在MySQL的高可用集群环境中,中间件是不可缺少的一部分,它提供了读写分离、负载均衡等各种功能,满足集群的横向、纵向的可扩展。由于官方并没有在这方面推出好的产品,更多的是第三方的产品。如:

  • ProxySQL #Percona
  • MaxScale #MariaDB
  • Atlas #360开源
  • OneProxy #平民软件楼方鑫
  • MyCat #社区推广
  • KingShard #原Atlas作者离职后使用go开发
  • TDDL #阿里巴巴开源
  • Cobar #阿里巴巴开源
  • DBProxy #美团在360Atlas上修改后开源
  • Fabric #官方产品
  • DRDS #阿里云分库分表产品

本次以测试ProxySQL为例,逐步了解ProxySQL的使用方式。

准备

环境:
ProxySQL: 1.4.1
Master: 118.190.67.67
Slave: 139.196.95.103(192.168.7.50)

安装配置详解

官网:http://www.proxysql.com/
Percona地址:https://www.percona.com/downloads/proxysql/
Github地址:https://github.com/sysown/proxysql/
本文通过作者编译好的rpm安装,也可通过编译安装的方式安装,本文省略

安装

下载proxysql可以有三种途径,分别为官网、Percona网站和Github网站
本文从github上下载最新稳定版本,这里选择centos67对应的rpm包
下载:wget -c -O proxysql-1.4.1-1-centos67.x86_64.rpm https://github.com/sysown/proxysql/releases/download/v1.4.1/proxysql-1.4.1-1-centos67.x86_64.rpm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@iZuf6c08fdv8duubho2b0rZ test]# yum localinstall -y proxysql-1.4.1-1-centos67.x86_64.rpm
Loaded plugins: security
docker-main-repo | 2.9 kB 00:00
Setting up Install Process
Resolving Dependencies
There are unfinished transactions remaining. You might consider running yum-complete-transaction first to finish them.
--> Running transaction check
---> Package proxysql.x86_64 0:1.4.1-1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

========================================================================================================================================================================
Package Arch Version Repository Size
========================================================================================================================================================================
Installing:
proxysql x86_64 1.4.1-1 /proxysql-1.4.1-1-centos67.x86_64 19 M

Transaction Summary
========================================================================================================================================================================
Install 1 Package(s)

Total size: 19 M
Installed size: 19 M
Downloading Packages:
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
Installing : proxysql-1.4.1-1.x86_64 1/1
Verifying : proxysql-1.4.1-1.x86_64 1/1

Installed:
proxysql.x86_64 0:1.4.1-1

Complete!

ProxySQL默认配置文件为/etc/proxysql.cnf,只在第一次启动的时候有用,后续的所有配置都是通过对SQLite数据库的操作,并且不会更新到proxysql中,而是存储在/var/lib/proxysql/proxysql.db中

1
2
3
4
5
6
7
8
9
[root@jiessie test]#  proxysql --version    #查看版本
ProxySQL version 1.4.1-45-gab4e6ee, codename Truls
[root@jiessie test]# rpm -ql proxysql #查看安装的具体内容
/etc/init.d/proxysql #启动脚本
/etc/proxysql.cnf #默认配置文件
/usr/bin/proxysql #执行文件
/usr/share/proxysql/tools/proxysql_galera_checker.sh #ProxySQL调度程序检查pxc_maint_mode参数状态,持续检测各个节点的状态
/usr/share/proxysql/tools/proxysql_galera_writer.pl #ProxySQL指定一个节点直接将流量写入galera
[root@jiessie test]#

启动

启动之后才会生成存储目录/var/lib/proxysql

1
2
3
4
5
6
7
8
[root@jiessie test]# /etc/init.d/proxysql start
Starting ProxySQL: DONE!
[root@jiessie test]# ll /var/lib/proxysql/
总用量 108
-rw------- 1 root root 98304 8月 29 15:37 proxysql.db
-rw------- 1 root root 4306 8月 29 16:25 proxysql.log
-rw-r--r-- 1 root root 5 8月 29 16:25 proxysql.pid
[root@jiessie test]#

内置对象介绍

登录

启动了6032和6033两个端口,默认管理端口是6032,客户端服务端口是6033,默认的用户名密码都是 admin,通过mysql的客户端可以登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@jiessie test]# netstat -tunlp|grep proxysql
tcp 0 0 0.0.0.0:6032 0.0.0.0:* LISTEN 5181/proxysql
tcp 0 0 0.0.0.0:6033 0.0.0.0:* LISTEN 5181/proxysql
[root@jiessie test]# mysql -uadmin -padmin -h127.0.0.1 -P6032
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.5.30 (ProxySQL Admin Module)

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)] 16:28:32 > show databases;
+-----+---------+-------------------------------+
| seq | name | file |
+-----+---------+-------------------------------+
| 0 | main | |
| 2 | disk | /var/lib/proxysql/proxysql.db |
| 3 | stats | |
| 4 | monitor | |
+-----+---------+-------------------------------+
4 rows in set (0.00 sec)

MySQL [(none)] 16:28:41 >

内置库

main:默认数据库名,用于存放后端db实例、用户认证、路由规则等信息。表名以runtime_开头的表示proxysql当前运行的配置内容,不能通过dml语句修改。只能修改对应的不以runtime_开头的(在内存)里的表,然后LOAD使其生效,SAVE使其存到硬盘以供下次重启加载。
disk:是持久化到硬盘的配置,sqlite数据文件。
stats:是proxysql运行抓取的统计信息,包括到后端各命令的执行次数、流量、processlist、查询各类汇总、执行时间等。
monitor:库存储monitor模块收集的信息,主要是对后端db的健康、延迟检查。

main库

runtime_表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
MySQL [main] 17:19:10 > use main
Database changed
MySQL [main] 17:22:15 > show tables;
+--------------------------------------------+
| tables |
+--------------------------------------------+
| global_variables |
| mysql_collations |
| mysql_group_replication_hostgroups |
| mysql_query_rules |
| mysql_replication_hostgroups |
| mysql_servers |
| mysql_users |
| proxysql_servers |
| runtime_global_variables |
| runtime_mysql_group_replication_hostgroups |
| runtime_mysql_query_rules |
| runtime_mysql_replication_hostgroups |
| runtime_mysql_servers |
| runtime_mysql_users |
| runtime_proxysql_servers |
| runtime_scheduler |
| scheduler |
+--------------------------------------------+
17 rows in set (0.00 sec)

MySQL [main] 17:22:16 >

其中,runtime_开关的表如下:

  • runtime_global_variables:global_variables的运行时版本
  • runtime_mysql_group_replication_hostgroups:mysql_group_replication_hostgroups的运行时版本
  • runtime_mysql_query_rules:mysql_query_rules的运行时版本
  • runtime_mysql_replication_hostgroups:mysql_replication_hostsgroups的运行时版本
  • runtime_mysql_servers:mysql_servers的运行时版本
  • runtime_mysql_users:mysql_users的运行时版本
  • runtime_scheduler:scheduler调度程序的运行时版本

    global_variables表

    内置参数表,参考下文

    mysql_servers表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    MySQL [main] 17:22:16 > show create table mysql_servers\G
    *************************** 1. row ***************************
    table: mysql_servers
    Create Table: CREATE TABLE mysql_servers (
    hostgroup_id INT NOT NULL DEFAULT 0,
    hostname VARCHAR NOT NULL,
    port INT NOT NULL DEFAULT 3306,
    status VARCHAR CHECK (UPPER(status) IN ('ONLINE','SHUNNED','OFFLINE_SOFT', 'OFFLINE_HARD')) NOT NULL DEFAULT 'ONLINE',
    weight INT CHECK (weight >= 0) NOT NULL DEFAULT 1,
    compression INT CHECK (compression >=0 AND compression <= 102400) NOT NULL DEFAULT 0,
    max_connections INT CHECK (max_connections >=0) NOT NULL DEFAULT 1000,
    max_replication_lag INT CHECK (max_replication_lag >= 0 AND max_replication_lag <= 126144000) NOT NULL DEFAULT 0,
    use_ssl INT CHECK (use_ssl IN(0,1)) NOT NULL DEFAULT 0,
    max_latency_ms INT UNSIGNED CHECK (max_latency_ms>=0) NOT NULL DEFAULT 0,
    comment VARCHAR NOT NULL DEFAULT '',
    PRIMARY KEY (hostgroup_id, hostname, port) )
    1 row in set (0.00 sec)

    MySQL [main] 17:34:05 >
  • hostgroup_id:ProxySQL通过hostgroup的形式组织后端db实例,一个hostgroup代表同属于一个角色。
    表的主键是(hostgroup_id, hostname, port),以hostname:port在多个hostgroup中存在。
    一个hostgroup可以有多个实例,即是多个从库,可能通过weight分配权重。
    hostgroup_id 0是一个特殊的hostgroup,路由查询的时候,没有匹配到规则则默认选择hostgroup 0。

  • status:
    ONLINE:当前后端实例状态正常。
    SHUNNED:临时被剔除,可能因为后端too many connection error,或者超过了max_replication_lag。
    OFFLINE_SOFT:软离线状态,不再接受新的连接,但已建立的连接会等待活跃事务完成。
    OFFLINE_HARD:硬离线状态,不再接受新的连接,已建立的连接或被强制中断,当后端实例宕机或网络不可达,会出现。
  • max_connections:允许连接到该后端实例的最大连接数,不要大于MySQL的max_connections。
    如果后端实例hostname:port在多个hostgroup里,以较大者为准,而不是各自独立允许的最大连接数。
  • max_replication_lag:允许的最大延迟,主库不受影响,默认为0,如果>0,monitor模块监控主从延迟大于阈值时,会临时把它的状态变更为SHUNNED。
  • max_latency_ms:mysql_ping响应时间,大于这个阈值会把它从连接池剔除,即使是ONLINE。
  • comment:备注,不建设为空。
  • 其他的字段,可通过字面意思理解。

    mysql_users表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    MySQL [main] 09:13:02 > show create table mysql_users\G
    *************************** 1. row ***************************
    table: mysql_users
    Create Table: CREATE TABLE mysql_users (
    username VARCHAR NOT NULL,
    password VARCHAR,
    active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,
    use_ssl INT CHECK (use_ssl IN (0,1)) NOT NULL DEFAULT 0,
    default_hostgroup INT NOT NULL DEFAULT 0,
    default_schema VARCHAR,
    schema_locked INT CHECK (schema_locked IN (0,1)) NOT NULL DEFAULT 0,
    transaction_persistent INT CHECK (transaction_persistent IN (0,1)) NOT NULL DEFAULT 1,
    fast_forward INT CHECK (fast_forward IN (0,1)) NOT NULL DEFAULT 0,
    backend INT CHECK (backend IN (0,1)) NOT NULL DEFAULT 1,
    frontend INT CHECK (frontend IN (0,1)) NOT NULL DEFAULT 1,
    max_connections INT CHECK (max_connections >=0) NOT NULL DEFAULT 10000,
    PRIMARY KEY (username, backend),
    UNIQUE (username, frontend))
    1 row in set (0.00 sec)

    MySQL [main] 09:13:08 >
  • username,password:连接到后端MySQL或ProxySQL实例的凭证,参考密码管理。
    密码可插入明文,也可通过PASSWORD()插入密文,proxysql以*开头判断插入是否是密文。
    但是runtime_mysql_users里统一是密文,所以明文插入,再SAVE MYSQL USERS TO MEM,此时看到的也是HASH密文。

  • active:是否生效该用户,active=0的用户将在数据库中被跟踪,但不会加载到内存中的数据结构中。
  • default_hostgroup:这个用户的请求没有匹配到规则时,默认发到hostgroup,默认0。
  • default_schema:这个用户连接时没有指定schema时,默认使用的schema。
    默认为NULL,实际上受变量mysql-default_schema的影响,默认为information_schema。
  • transaction_persistent: 如果设置为1,连接上ProxySQL的会话后,如果在一个hostgroup上开启了事务,那么后续的sql都继续维持在这个hostgroup上,不论是否会匹配上其它路由规则,直到事务结束。
  • frontend:如果设置为1,则用户名、密码对ProxySQL进行身份验证。
  • backend:如果设置为1,则用户名、密码根据任何主机组向mysqld服务器进行身份验证。
    注意,目前所有用户都需要将“前端”和“后端“都设置为1,未来版本的ProxySQL将分离前端和后端之间的crendentials。以这种方式,前端将永远不会知道直接连接到后端的凭据,强制所有通过ProxySQL的连接并增加系统的安全性。

    mysql_replication_hostgroups表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    MySQL [main] 10:18:15 > show create table mysql_replication_hostgroups\G
    *************************** 1. row ***************************
    table: mysql_replication_hostgroups
    Create Table: CREATE TABLE mysql_replication_hostgroups (
    writer_hostgroup INT CHECK (writer_hostgroup>=0) NOT NULL PRIMARY KEY,
    reader_hostgroup INT NOT NULL CHECK (reader_hostgroup<>writer_hostgroup AND reader_hostgroup>0),
    comment VARCHAR,
    UNIQUE (reader_hostgroup))
    1 row in set (0.00 sec)

    MySQL [main] 10:18:22 >
  • 定义hostgroup的主从关系。ProxySQL monitor模块会监控hostgroup后端所有servers的read_only变量,如果发现从库的read_only变为0、主库变为1,则认为角色互换了,自动改写mysql_servers表里面hostgroup关系,达到failover效果。

    mysql_query_rules查询规则表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    MySQL [main] 10:25:22 > show create table mysql_query_rules\G           
    *************************** 1. row ***************************
    table: mysql_query_rules
    Create Table: CREATE TABLE mysql_query_rules (
    rule_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 0,
    username VARCHAR,
    schemaname VARCHAR,
    flagIN INT NOT NULL DEFAULT 0,
    client_addr VARCHAR,
    proxy_addr VARCHAR,
    proxy_port INT,
    digest VARCHAR,
    match_digest VARCHAR,
    match_pattern VARCHAR,
    negate_match_pattern INT CHECK (negate_match_pattern IN (0,1)) NOT NULL DEFAULT 0,
    re_modifiers VARCHAR DEFAULT 'CASELESS',
    flagOUT INT,
    replace_pattern VARCHAR,
    destination_hostgroup INT DEFAULT NULL,
    cache_ttl INT CHECK(cache_ttl > 0),
    reconnect INT CHECK (reconnect IN (0,1)) DEFAULT NULL,
    timeout INT UNSIGNED,
    retries INT CHECK (retries>=0 AND retries <=1000),
    delay INT UNSIGNED,
    next_query_flagIN INT UNSIGNED,
    mirror_flagOUT INT UNSIGNED,
    mirror_hostgroup INT UNSIGNED,
    error_msg VARCHAR,
    OK_msg VARCHAR,
    sticky_conn INT CHECK (sticky_conn IN (0,1)),
    multiplex INT CHECK (multiplex IN (0,1,2)),
    log INT CHECK (log IN (0,1)),
    apply INT CHECK(apply IN (0,1)) NOT NULL DEFAULT 0,
    comment VARCHAR)
    1 row in set (0.00 sec)

    MySQL [main] 10:25:34 >
  • rule_id:表主键,自增,规则处理是以rule_id为顺序进行。

  • active:只有active=1时的规则才会参与匹配。
  • username:过滤匹配用户名的条件,如果是非空值,则仅当连接使用正确的用户名时,查询才匹配。
  • schemaname:匹配schemaname的过滤条件,如果是非空值,则仅当连接schemaname用作默认模式时,查询才匹配。
  • flagIN,flagOUT,apply:用来定义路由链chains of rules
    首先会检查flagIN=0的规则,以rule_id的顺序;如果没有匹配上,则走这个用户的default_hostgroup。
    当匹配一条规则后,会检查flagOUT。
    如果不为NULL,并且flagIN!=flagOUT,则进入以flagIN为上一个flagOUT值的新规则链。
    如果不为NULL,并且flagIN=flagOUT,则应用这条规则。
    如果为NULL,或者apply=1,则结束,应用这条规则。
    如果最终没有匹配到,则找到这个用户的default_hostgroup。
  • client_addr:匹配客户端来源IP。
  • proxy_addr,proxy_port:匹配本地proxysql的ip、端口。
  • digest:精确匹配的查询。
  • match_digest:正则匹配查询。query,digest是指对查询去掉具体值后进行”模糊化“后的查询,类似pt-query-digest的效果。
  • match_pattern:正则匹配查询。
    以上都是匹配查询的规则,1.4版本可以通过变量mysql-query_processor_regex设置,支持RE2和PCRE,1.4版本开始默认为PCRE。
  • negate_match_pattern:反向匹配,相当于对match_digest/match_pattern的匹配取反。
  • re_modifiers:修改正则匹配的参数,比如默认的:忽略大小写CASELESS、禁用GLOBAL。
  • 下面是匹配后的行为:
  • replace_pattern:查询重写,默认为空。
  • destination_hostgroup:路由查询到这个hostgroup,当然如果用户显式start transaction且transaction_persistent=1,那么即使匹配到了,也依然按照事务里第一条sql的路由规则去走的。
  • cache_ttl:查询结果缓存的毫秒数。
  • timeout:这一类查询执行的的最大时间(毫秒),超时则自动kill。
    这是对后端DB的保护机制,相当于阿里云RDS的loose_max_statement_time变量的功能,但不同的是,阿里云这个变量的时间时不包括DML操作出现InnoDB行锁等待的时间,而ProxySQL的这个timeout是计算从发送sql到等待响应的时间。默认mysql-default_query_timeout是10h。
  • retries:语句在执行失败时,重试次数。默认由mysql-query_retries_on_failure变量指定,为1。建议不要重试,有风险。
  • delay:查询延迟执行,这是ProxySQL提供的限流机制,会让其它的查询优先执行。
    默认值mysql-default_query_delay为0。
  • mirror_flagOUT,mirror_hostgroup:与镜像相关的设置。
  • error_msg:默认为NULL,如果指定了则这个查询直接被block掉,将error_msg返回给客户端。
  • multiplex:连接是否利用,请参考文章。
  • log:是否记录查询日志,可以看到log是否记录的对象是根据规则。
    要开启日志记录,需要设置变量mysql-eventslog_filename来指定文件名,然后这个log标记为1。但是目前proxysql记录的日志是二进制格式,需要特定的工具才能读取:eventslog_reader_sample。这个工具在源码目录 tools下面。

    scheduler调度表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    MySQL [main] 10:27:52 > show create table scheduler\G
    *************************** 1. row ***************************
    table: scheduler
    Create Table: CREATE TABLE scheduler (
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    active INT CHECK (active IN (0,1)) NOT NULL DEFAULT 1,
    interval_ms INTEGER CHECK (interval_ms>=100 AND interval_ms<=100000000) NOT NULL,
    filename VARCHAR NOT NULL,
    arg1 VARCHAR,
    arg2 VARCHAR,
    arg3 VARCHAR,
    arg4 VARCHAR,
    arg5 VARCHAR,
    comment VARCHAR NOT NULL DEFAULT '')
    1 row in set (0.00 sec)

    MySQL [main] 11:09:59 >
  • id:调度程序作业的唯一标识符。

  • active:如果设置为1,则作业处于活动状态。
  • interval_ms:工作的开始频率(以毫秒为单位),最小interval_ms为100毫秒。
  • filename:可执行文件的完整路径。
  • arg1-arg5:传递作业的参数。最多5个。
  • comment:注释。
  • 参考文档

    disk库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    MySQL [main] 15:12:14 > show tables from disk;
    +------------------------------------+
    | tables |
    +------------------------------------+
    | global_variables |
    | mysql_collations |
    | mysql_group_replication_hostgroups |
    | mysql_query_rules |
    | mysql_replication_hostgroups |
    | mysql_servers |
    | mysql_users |
    | proxysql_servers |
    | scheduler |
    +------------------------------------+
    9 rows in set (0.00 sec)

    MySQL [main] 15:12:26 >

具体的表介绍和main库一致。

stats库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MySQL [main] 15:12:26 > show tables from stats;
+-----------------------------------+
| tables |
+-----------------------------------+
| global_variables |
| stats_memory_metrics |
| stats_mysql_commands_counters |
| stats_mysql_connection_pool |
| stats_mysql_connection_pool_reset |
| stats_mysql_global |
| stats_mysql_processlist |
| stats_mysql_query_digest |
| stats_mysql_query_digest_reset |
| stats_mysql_query_rules |
| stats_mysql_users |
| stats_proxysql_servers_metrics |
| stats_proxysql_servers_status |
+-----------------------------------+
13 rows in set (0.00 sec)

MySQL [main] 15:13:53 >
stats_mysql_commands_counters表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MySQL [stats] 15:15:36 > show create table stats.stats_mysql_commands_counters\G
*************************** 1. row ***************************
table: stats_mysql_commands_counters
Create Table: CREATE TABLE stats_mysql_commands_counters (
Command VARCHAR NOT NULL PRIMARY KEY,
Total_Time_us INT NOT NULL,
Total_cnt INT NOT NULL,
cnt_100us INT NOT NULL,
cnt_500us INT NOT NULL,
cnt_1ms INT NOT NULL,
cnt_5ms INT NOT NULL,
cnt_10ms INT NOT NULL,
cnt_50ms INT NOT NULL,
cnt_100ms INT NOT NULL,
cnt_500ms INT NOT NULL,
cnt_1s INT NOT NULL,
cnt_5s INT NOT NULL,
cnt_10s INT NOT NULL,
cnt_INFs)
1 row in set (0.00 sec)

MySQL [stats] 15:15:58 >
  • command:已执行的SQL命令的类型,如FLUSH、INSERT、KILL、SELECT FOR UPDATE等。
  • Total_Time_us:执行该类型命令的总时间(以毫秒为单位)。
  • Total_cnt:执行该类型的命令的总数。
  • cnt_100us-cnt_INFs:在指定的时间限制内执行的给定类型的命令总数和前一个命令的总数。
    ##### stats_mysql_connection_pool表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    MySQL [stats] 15:15:58 > show create table stats.stats_mysql_connection_pool \G 
    *************************** 1. row ***************************
    table: stats_mysql_connection_pool
    Create Table: CREATE TABLE stats_mysql_connection_pool (
    hostgroup INT,
    srv_host VARCHAR,
    srv_port INT,
    status VARCHAR,
    ConnUsed INT,
    ConnFree INT,
    ConnOK INT,
    ConnERR INT,
    Queries INT,
    Bytes_data_sent INT,
    Bytes_data_recv INT,
    Latency_us INT)
    1 row in set (0.00 sec)

    MySQL [stats] 15:20:08 >
  • hostgroup:后端服务器所属的主机组,单个后端服务器可以属于多个主机组。

  • srv_host,srv_port:mysqld后端服务器正在侦听连接的TCP端点的IP和Port。
  • status:后端服务器的状态。可以有ONLINE,SHUNNED,OFFLINE_SOFT,OFFLINE_HARD。
  • ConnUsed:ProxySQL当前使用多少个连接来向后端服务器发送查询。
  • ConnFree:目前有多少个连接是空闲。
  • ConnOK:成功建立了多少个连接。
  • ConnERR:没有成功建立多少个连接。
  • Queries:路由到此特定后端服务器的查询数。
  • Bytes_data_sent:发送到后端的数据量。
  • Bytes_data_recv:从后端接收的数据量。
  • Latency_ms:从Monitor报告的当前ping以毫秒为单位的延迟时间。

    stats_mysql_global表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    MySQL [stats] 15:20:08 > show create table stats.stats_mysql_global\G          
    *************************** 1. row ***************************
    table: stats_mysql_global
    Create Table: CREATE TABLE stats_mysql_global (
    Variable_Name VARCHAR NOT NULL PRIMARY KEY,
    Variable_Value VARCHAR NOT NULL)
    1 row in set (0.00 sec)

    MySQL [stats] 15:22:35 >
  • Variable_Name:代表与MySQL相关的代理级别的全局统计
    如Client_Connections_aborted:由于无效凭据或max_connections而导致的前端连接数已达到;
    如Client_Connections_connected:当前连接的前端连接数。
    如Client_Connections_created:到目前为止创建的前端连接数。等等。

  • Variable_Value:统计所对应的值。

    stats_mysql_processlist表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    MySQL [stats] 15:24:11 > show create table stats.stats_mysql_processlist\G
    *************************** 1. row ***************************
    table: stats_mysql_processlist
    Create Table: CREATE TABLE stats_mysql_processlist (
    ThreadID INT NOT NULL,
    SessionID INTEGER PRIMARY KEY,
    user VARCHAR,
    db VARCHAR,
    cli_host VARCHAR,
    cli_port INT,
    hostgroup INT,
    l_srv_host VARCHAR,
    l_srv_port INT,
    srv_host VARCHAR,
    srv_port INT,
    command VARCHAR,
    time_ms INT NOT NULL,
    info VARCHAR)
    1 row in set (0.00 sec)

    MySQL [stats] 15:26:43 >
  • ThreadID:ProxySQL线程的内部ID。

  • SessionID:ProxySQL会话ID,通过这个ID可以进行kill操作。
  • user:与MySQL客户端连接到ProxySQL的用户。
  • db:当前选择的数据库。
  • cli_host,cli_port:连接ProxySQL的IP和TCP端口。
  • hostgroup:当前主机组。如果正在处理查询,则是查询已被路由或将要路由的主机组,或默认主机组。可以通过这个查看该SQL到底是到哪个HG里。
  • l_srv_host,l_srv_port:ProxySQL的IP和TCP端口。
  • srv_host,srv_port:后端MySQL服务器的IP和端口。
  • command:正在执行的MySQL查询的类型。
  • time_ms:命令执行的时间(以毫秒为单位)。
  • info:正在执行的SQL。

    stats_mysql_query_digest表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    MySQL [stats] 15:26:43 > show create table stats.stats_mysql_query_digest\G
    *************************** 1. row ***************************
    table: stats_mysql_query_digest
    Create Table: CREATE TABLE stats_mysql_query_digest (
    hostgroup INT,
    schemaname VARCHAR NOT NULL,
    username VARCHAR NOT NULL,
    digest VARCHAR NOT NULL,
    digest_text VARCHAR NOT NULL,
    count_star INTEGER NOT NULL,
    first_seen INTEGER NOT NULL,
    last_seen INTEGER NOT NULL,
    sum_time INTEGER NOT NULL,
    min_time INTEGER NOT NULL,
    max_time INTEGER NOT NULL,
    PRIMARY KEY(hostgroup, schemaname, username, digest))
    1 row in set (0.00 sec)

    MySQL [stats] 15:29:27 >
  • hostgroup:发送查询的主机组。值-1表示查询查询缓存。

  • schemaname:查询的数据库。
  • user:连接ProxySQL的用户名。
  • digest:一个十六进制散列,表示其参数剥离的SQL。
  • digest_text:参数剥离的实际SQL文本
  • count_star:执行查询的总次数(参数的值不同)。
  • first_seen:unix时间戳,是通过代理路由查询的第一时刻。
  • last_seen:unix时间戳,当查询通过代理路由时的最后一刻(到目前为止)。
  • sum_time:执行此类查询的总时间(以微秒为单位)。
    这对于确定应用程序工作负载中花费的最多时间在哪里是非常有用的,并为改进的地方提供了一个良好的起点。
  • min_time,max_time - 执行此类查询时期望的持续时间范围。
    min_time是到目前为止所看到的最小执行时间,而max_time表示最大执行时间,以微秒为单位。

    stats_mysql_query_rules表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    MySQL [stats] 15:29:27 > show create table stats.stats_mysql_query_rules\G 
    *************************** 1. row ***************************
    table: stats_mysql_query_rules
    Create Table: CREATE TABLE stats_mysql_query_rules (
    rule_id INTEGER PRIMARY KEY,
    hits INT NOT NULL)
    1 row in set (0.00 sec)

    MySQL [stats] 15:31:57 >
  • rule_id:路由规则的ID与main.mysql_query_rules的id对应。

  • hits:此路由规则的匹配总数。 如果当前传入的查询符合规则,则会记录一次命中。

    monitor库

    对后端MySQL的健康检查,由变量mysql-monitor_enabled来确定是否开启Monitor模块。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    MySQL [stats] 15:35:32 > show tables from monitor;                               
    +------------------------------------+
    | tables |
    +------------------------------------+
    | mysql_server_connect |
    | mysql_server_connect_log |
    | mysql_server_group_replication_log |
    | mysql_server_ping |
    | mysql_server_ping_log |
    | mysql_server_read_only_log |
    | mysql_server_replication_lag_log |
    +------------------------------------+
    7 rows in set (0.00 sec)

    MySQL [stats] 15:35:52 >
mysql_server_connect/mysql_server_connect_log表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
MySQL [stats] 15:37:39 > show create table monitor.mysql_server_connect\G  
*************************** 1. row ***************************
table: mysql_server_connect
Create Table: CREATE TABLE mysql_server_connect (
hostname VARCHAR NOT NULL,
port INT NOT NULL DEFAULT 3306,
time_since INT NOT NULL DEFAULT 0,
time_until INT NOT NULL DEFAULT 0,
connect_success_count INT NOT NULL DEFAULT 0,
connect_success_first INT NOT NULL DEFAULT 0,
connect_success_last INT NOT NULL DEFAULT 0,
connect_success_time_min INT NOT NULL DEFAULT 0,
connect_success_time_max INT NOT NULL DEFAULT 0,
connect_success_time_total INT NOT NULL DEFAULT 0,
connect_failure_count INT NOT NULL DEFAULT 0,
connect_failure_first INT NOT NULL DEFAULT 0,
connect_failure_last INT NOT NULL DEFAULT 0,
PRIMARY KEY (hostname, port))
1 row in set (0.00 sec)

MySQL [stats] 15:37:46 > show create table monitor.mysql_server_connect_log\G
*************************** 1. row ***************************
table: mysql_server_connect_log
Create Table: CREATE TABLE mysql_server_connect_log (
hostname VARCHAR NOT NULL,
port INT NOT NULL DEFAULT 3306,
time_start_us INT NOT NULL DEFAULT 0,
connect_success_time_us INT DEFAULT 0,
connect_error VARCHAR,
PRIMARY KEY (hostname, port, time_start_us))
1 row in set (0.00 sec)

MySQL [stats] 15:37:51 >
  • 连接到所有MySQL服务器以检查它们是否可用,该表用来存放检测连接的日志。由变量mysql-monitor_connect_interval来控制其检测的时间间隔,由参数mysql-monitor_connect_timeout控制连接是否超时(默认200毫秒)。

    mysql_server_ping/mysql_server_ping_log表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    MySQL [stats] 15:39:23 > show create table monitor.mysql_server_ping\G       
    *************************** 1. row ***************************
    table: mysql_server_ping
    Create Table: CREATE TABLE mysql_server_ping (
    hostname VARCHAR NOT NULL,
    port INT NOT NULL DEFAULT 3306,
    time_since INT NOT NULL DEFAULT 0,
    time_until INT NOT NULL DEFAULT 0,
    ping_success_count INT NOT NULL DEFAULT 0,
    ping_success_first INT NOT NULL DEFAULT 0, ping_success_last INT NOT NULL DEFAULT 0,
    ping_success_time_min INT NOT NULL DEFAULT 0,
    ping_success_time_max INT NOT NULL DEFAULT 0,
    ping_success_time_total INT NOT NULL DEFAULT 0,
    ping_failure_count INT NOT NULL DEFAULT 0,
    ping_failure_first INT NOT NULL DEFAULT 0,
    ping_failure_last INT NOT NULL DEFAULT 0,
    PRIMARY KEY (hostname, port))
    1 row in set (0.00 sec)

    MySQL [stats] 15:40:12 > show create table monitor.mysql_server_ping_log\G
    *************************** 1. row ***************************
    table: mysql_server_ping_log
    Create Table: CREATE TABLE mysql_server_ping_log (
    hostname VARCHAR NOT NULL,
    port INT NOT NULL DEFAULT 3306,
    time_start_us INT NOT NULL DEFAULT 0,
    ping_success_time_us INT DEFAULT 0,
    ping_error VARCHAR,
    PRIMARY KEY (hostname, port, time_start_us))
    1 row in set (0.00 sec)

    MySQL [stats] 15:40:15 >
  • 使用mysql_ping API ping后端MySQL服务器检查它们是否可用,该表用来存放ping的日志。由变量mysql-monitor_ping_interval控制ping的时间间隔,默认值:10000(毫秒,相当于10秒)。

    mysql_server_replication_lag_log表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    MySQL [stats] 15:40:53 > show create table monitor.mysql_server_replication_lag_log\G
    *************************** 1. row ***************************
    table: mysql_server_replication_lag_log
    Create Table: CREATE TABLE mysql_server_replication_lag_log (
    hostname VARCHAR NOT NULL,
    port INT NOT NULL DEFAULT 3306,
    time_start_us INT NOT NULL DEFAULT 0,
    success_time_us INT DEFAULT 0,
    repl_lag INT DEFAULT 0,
    error VARCHAR,
    PRIMARY KEY (hostname, port, time_start_us))
    1 row in set (0.00 sec)

    MySQL [stats] 15:41:12 >
  • 后端MySQL服务主从延迟的检测。由参数mysql-monitor_replication_lag_interval控制检测间隔时间, 如果复制滞后太大,可以暂时关闭从。由mysql_servers.max_replication_lag列控制。默认值:10000(毫秒,相当于10秒)。

内置参数

global_variables 1.4版本中有95个参数,参数较多,解释请参考文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
MySQL [main] 11:16:22 > show variables;
+-----------------------------------------------------+--------------------+
| Variable_name | Value |
+-----------------------------------------------------+--------------------+
| admin-admin_credentials | admin:admin |
| admin-cluster_check_interval_ms | 1000 |
| admin-cluster_password | |
| admin-cluster_username | |
| admin-hash_passwords | true |
| admin-mysql_ifaces | 0.0.0.0:6032 |
| admin-read_only | false |
| admin-refresh_interval | 2000 |
| admin-stats_credentials | stats:stats |
| admin-telnet_admin_ifaces | (null) |
| admin-telnet_stats_ifaces | (null) |
| admin-version | 1.4.1-45-gab4e6ee |
| mysql-client_found_rows | true |
| mysql-commands_stats | true |
| mysql-connect_retries_delay | 1 |
| mysql-connect_retries_on_failure | 10 |
| mysql-connect_timeout_server | 3000 |
| mysql-connect_timeout_server_max | 10000 |
| mysql-connection_delay_multiplex_ms | 0 |
| mysql-connection_max_age_ms | 0 |
| mysql-default_charset | utf8 |
| mysql-default_max_latency_ms | 1000 |
| mysql-default_query_delay | 0 |
| mysql-default_query_timeout | 36000000 |
| mysql-default_reconnect | true |
| mysql-default_schema | information_schema |
| mysql-default_sql_mode | |
| mysql-default_time_zone | SYSTEM |
| mysql-enforce_autocommit_on_reads | false |
| mysql-eventslog_filename | |
| mysql-eventslog_filesize | 104857600 |
| mysql-forward_autocommit | false |
| mysql-free_connections_pct | 10 |
| mysql-have_compress | true |
| mysql-hostgroup_manager_verbose | 1 |
| mysql-init_connect | (null) |
| mysql-interfaces | 0.0.0.0:6033 |
| mysql-long_query_time | 1000 |
| mysql-max_allowed_packet | 4194304 |
| mysql-max_connections | 2048 |
| mysql-max_stmts_cache | 10000 |
| mysql-max_stmts_per_connection | 20 |
| mysql-max_transaction_time | 14400000 |
| mysql-mirror_max_concurrency | 16 |
| mysql-mirror_max_queue_length | 32000 |
| mysql-monitor_connect_interval | 60000 |
| mysql-monitor_connect_timeout | 600 |
| mysql-monitor_enabled | true |
| mysql-monitor_groupreplication_healthcheck_interval | 5000 |
| mysql-monitor_groupreplication_healthcheck_timeout | 800 |
| mysql-monitor_history | 600000 |
| mysql-monitor_password | monitor |
| mysql-monitor_ping_interval | 10000 |
| mysql-monitor_ping_max_failures | 3 |
| mysql-monitor_ping_timeout | 1000 |
| mysql-monitor_query_interval | 60000 |
| mysql-monitor_query_timeout | 100 |
| mysql-monitor_read_only_interval | 1500 |
| mysql-monitor_read_only_timeout | 500 |
| mysql-monitor_replication_lag_interval | 10000 |
| mysql-monitor_replication_lag_timeout | 1000 |
| mysql-monitor_slave_lag_when_null | 60 |
| mysql-monitor_username | monitor |
| mysql-monitor_wait_timeout | true |
| mysql-monitor_writer_is_also_reader | true |
| mysql-multiplexing | true |
| mysql-ping_interval_server_msec | 120000 |
| mysql-ping_timeout_server | 500 |
| mysql-poll_timeout | 2000 |
| mysql-poll_timeout_on_failure | 100 |
| mysql-query_cache_size_MB | 256 |
| mysql-query_digests | true |
| mysql-query_digests_lowercase | false |
| mysql-query_digests_max_digest_length | 2048 |
| mysql-query_digests_max_query_length | 65000 |
| mysql-query_processor_iterations | 0 |
| mysql-query_processor_regex | 1 |
| mysql-query_retries_on_failure | 1 |
| mysql-server_capabilities | 45578 |
| mysql-server_version | 5.5.30 |
| mysql-servers_stats | true |
| mysql-session_idle_ms | 1000 |
| mysql-session_idle_show_processlist | true |
| mysql-sessions_sort | true |
| mysql-shun_on_failures | 5 |
| mysql-shun_recovery_time_sec | 10 |
| mysql-ssl_p2s_ca | (null) |
| mysql-ssl_p2s_cert | (null) |
| mysql-ssl_p2s_cipher | (null) |
| mysql-ssl_p2s_key | (null) |
| mysql-stacksize | 1048576 |
| mysql-threads | 4 |
| mysql-threshold_query_length | 524288 |
| mysql-threshold_resultset_size | 4194304 |
| mysql-wait_timeout | 28800000 |
+-----------------------------------------------------+--------------------+
95 rows in set (0.00 sec)

MySQL [main] 11:16:33 >

ProxySQL多层配置设计

ProxySQL设计模型介绍

ProxySQL使用多层配置系统,适合满足以下需求:

  • 允许自动更新配置,与MySQL兼容管理界面;
  • 允许在线修改配置,不用重启ProxySQL;
  • 允许回滚配置;
    多层配置系统的实现,如下图:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    +-------------------------+
    | RUNTIME |
    +-------------------------+
    /|\ |
    | |
    [1] | [2] |
    | \|/
    +-------------------------+
    | MEMORY |
    +-------------------------+ _
    /|\ | |\
    | | \
    [3] | [4] | \ [5]
    | \|/ \
    +-------------------------+ +-------------------------+
    | DISK | | CONFIG FILE |
    +-------------------------+ +-------------------------+
  • RUNTIME代表ProxySQL当前生效的配置,包括global_variables、mysql_servers、mysql_users、mysql_query_rules。无法直接修改这里的配置,必须要从下一层load过来。

  • MEMORY(main)代表平时在mysql命令行修改的main里的配置,可以认为是SQLite数据库在内存的镜像。可修改以下:

    1
    2
    3
    4
    5
    mysql_server        后端服务器列表
    mysql_users 连接到ProxySQL的用户列表及其凭据
    mysql_query_rules 将流量路由到不同的后端服务器的规则列表
    global_variables 全局变量列表
    mysql_collat MySQL排序规则列表
  • DISK和CONFIG FILE表示磁盘上SQLite数据库,默认位置在$datadir/proxysql.db,在重新启动过程中,内存中未被保存的配置将丢失。/etc/proxysql.cnf文件只在第一次初始化的时候用到。如要修改端口,还是需要在管理命令行里修改,再save到磁盘。

    ProxySQL多层配置修改示例
  • mysql users

    1
    2
    3
    4
    5
    LOAD MYSQL USERS TO RUNTIME / LOAD MYSQL USERS FROM MEMORY
    SAVE MYSQL USERS TO MEMORY / SAVE MYSQL USERS FROM RUNTIME
    LOAD MYSQL USERS TO MEMORY / LOAD MYSQL USERS FROM DISK
    SAVE MYSQL USERS TO DISK / SAVE MYSQL USERS FROM MEMORY
    LOAD MYSQL USERS FROM CONFIG
  • mysql servers

    1
    2
    3
    4
    5
    LOAD MYSQL SERVERS TO RUNTIME   让修改的配置生效
    SAVE MYSQL SERVERS TO MEMORY
    LOAD MYSQL SERVERS TO MEMORY
    SAVE MYSQL SERVERS TO DISK 将修改的配置持久化
    LOAD MYSQL SERVERS FROM CONFIG
  • mysql query rules

    1
    2
    3
    4
    5
    load mysql query rules to run
    save mysql query rules to mem
    load mysql query rules to mem
    save mysql query rules to disk
    load mysql query rules from config
  • mysql variables

    1
    2
    3
    4
    5
    load mysql variables to runtime
    save mysql variables to memory
    load mysql variables to memory
    save mysql variables to disk
    load mysql variables from config
  • admin variables

    1
    2
    3
    4
    5
    load admin variables to runtime
    save admin variables to memory
    load admin variables to memory
    save admin variables to disk
    load admin variables from config
  • 参考

  • https://github.com/sysown/proxysql/wiki/Multi-layer-configuration-system
  • https://severalnines.com/blog/mysql-load-balancing-proxysql-overview
  • http://seanlook.com/2017/04/10/mysql-proxysql-install-config/

ProxySQL读写分离示例

准备

Master:118.190.67.67:3306
Slave :139.196.95.103:3306(192.168.7.50)
ProxySQL:139.196.95.103:3306(192.168.7.50)
版本:percona-server 5.7.18

安装配置

主从安装配置省略。

示例目标

客户端通过访问ProxySQL的ip,实际访问Master和Slave的效果。

添加后端DB服务

100是主库,101是从库,同时主库也处理1/10的读请求,登录ProxySQL管理端设置:

1
2
MySQL [(none)] 14:22:06 > insert into mysql_servers(hostgroup_id,hostname,port,weight,comment) values(100, '118.190.67.67', 3306, 1, 'db0,ReadWrite'),(101, '192.168.7.50', 3306, 9, 'db0,ReadOnly');
Query OK, 2 rows affected (0.00 sec)

添加访问用户

登录Master主库设置监控用户和程序用户(由于是测试使用,权限较大,主机允许所有):

1
2
3
4
5
6
7
8
9
10
11
MySQL [(none)] 15:51:32 > create user 'monitor'@'%' identified by 'monitor';
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:51:37 > grant select,super,process,show databases,replication client,replication slave on *.* to 'monitor'@'%';
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:45:23 > create user 'user0'@'%' identified by 'password0';
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 15:49:42 > GRANT SELECT, RELOAD, PROCESS, SHOW DATABASES, SUPER, LOCK TABLES, EXECUTE, SHOW VIEW, TRIGGER, EVENT ON *.* TO 'read0'@'%';
Query OK, 0 rows affected (0.00 sec)

登录ProxySQL管理端设置:
这里default_hostgroup指定了hostgroup为100的主库,下文会设置SELECT …FOR UPDATE规则到100,SELECT到101,其他所有的SQL到default_hostgroup,也就是主库。

1
2
MySQL [(none)] 14:22:15 > INSERT INTO mysql_users (username, password, active, default_hostgroup, max_connections) VALUES ('user0', 'password0', 1, 100, 1000); 
Query OK, 2 rows affected (0.00 sec)

添加复制关系

登录ProxySQL管理端设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
MySQL [main] 17:32:46 > INSERT INTO mysql_replication_hostgroups VALUES(100,101,'db0');
Query OK, 1 row affected (0.00 sec)

MySQL [main] 17:33:30 > load mysql variables to runtime;
Query OK, 0 rows affected (0.00 sec)

MySQL [main] 17:33:54 > save mysql variables to disk;
Query OK, 83 rows affected (0.01 sec)

MySQL [main] 17:35:53 > SELECT * FROM monitor.mysql_server_read_only_log ORDER BY time_start_us DESC LIMIT 10;
+---------------+------+------------------+-----------------+-----------+-------+
| hostname | port | time_start_us | success_time_us | read_only | error |
+---------------+------+------------------+-----------------+-----------+-------+
| 192.168.7.50 | 3306 | 1504085770195146 | 630 | 1 | NULL |
| 118.190.67.67 | 3306 | 1504085770194710 | 23722 | 0 | NULL |
| 192.168.7.50 | 3306 | 1504085768695087 | 650 | 1 | NULL |
| 118.190.67.67 | 3306 | 1504085768694620 | 23706 | 0 | NULL |
| 192.168.7.50 | 3306 | 1504085767194957 | 628 | 1 | NULL |
| 118.190.67.67 | 3306 | 1504085767194507 | 23686 | 0 | NULL |
| 192.168.7.50 | 3306 | 1504085765694834 | 634 | 1 | NULL |
| 118.190.67.67 | 3306 | 1504085765694387 | 23669 | 0 | NULL |
| 192.168.7.50 | 3306 | 1504085764194744 | 641 | 1 | NULL |
| 118.190.67.67 | 3306 | 1504085764194301 | 23729 | 0 | NULL |
+---------------+------+------------------+-----------------+-----------+-------+
10 rows in set (0.00 sec)

修改全局变量

登录ProxySQL管理端设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
MySQL [(none)] 14:25:07 > set mysql-query_retries_on_failure=0;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-max_stmts_per_connection=1000;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-eventslog_filename='queries.log';
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-monitor_slave_lag_when_null=7200;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-ping_timeout_server=1500;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-monitor_connect_timeout=1000;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-default_max_latency_ms=2000;
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-monitor_username='monitor';
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-monitor_password='monitor';
Query OK, 1 row affected (0.00 sec)

MySQL [(none)] 14:25:07 > set mysql-server_version='5.7.18';
Query OK, 1 row affected (0.00 sec)

全局变量生效并保存到磁盘:
登录ProxySQL管理端设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
MySQL [(none)] 14:25:07 > load mysql users to runtime;
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 14:25:27 > load mysql servers to runtime;
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 14:25:27 > load mysql variables to runtime;
Query OK, 0 rows affected (0.00 sec)

MySQL [(none)] 14:25:28 > save mysql users to disk;
Query OK, 0 rows affected (0.02 sec)

MySQL [(none)] 14:25:36 > save mysql servers to disk;
save mysql variables to disk;Query OK, 0 rows affected (0.03 sec)

MySQL [(none)] 14:25:37 > save mysql variables to disk;
Query OK, 83 rows affected (0.01 sec)

MySQL [(none)] 14:25:37 > save mysql users to mem; -- 可以屏蔽看到的明文密码
Query OK, 0 rows affected (0.00 sec)

路由规则

  • ProxySQL使用查询规则来确定路由,如果没有规则用于查询,默认会访问hostgroup 0主机组,会报以下错误:
    1
    2
    3
    [root@jiessie ~]# mysql -uuser0 -ppassword0 -h 127.0.0.1 -P6033 -e "SELECT 1"
    mysql: [Warning] Using a password on the command line interface can be insecure.
    ERROR 9001 (HY000) at line 1: Max connect timeout reached while reaching hostgroup 1 after 2000ms

设置路由规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
MySQL [(none)] 09:29:14 > use main;
Database changed
MySQL [main] 09:29:15 > INSERT INTO mysql_query_rules (active, match_pattern, destination_hostgroup, cache_ttl) VALUES (1, '^SELECT .* FOR UPDATE', 100, NULL);
Query OK, 1 row affected (0.00 sec)

MySQL [main] 09:29:17 > INSERT INTO mysql_query_rules (active, match_pattern, destination_hostgroup, cache_ttl) VALUES (1, '^SELECT .*', 101, NULL);
Query OK, 1 row affected (0.00 sec)

MySQL [main] 09:29:18 > load mysql query rules to run;
Query OK, 0 rows affected (0.00 sec)

MySQL [main] 09:29:56 > save mysql query rules to disk;
Query OK, 0 rows affected (0.03 sec)

常用查询

  • 查询连接日志:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    MySQL [(none)] 13:45:08 > SELECT * FROM monitor.mysql_server_connect_log ORDER BY time_start_us DESC LIMIT 10;
    +---------------+------+------------------+-------------------------+---------------+
    | hostname | port | time_start_us | connect_success_time_us | connect_error |
    +---------------+------+------------------+-------------------------+---------------+
    | 192.168.7.50 | 3306 | 1504590343795072 | 410 | NULL |
    | 118.190.67.67 | 3306 | 1504590343780010 | 69662 | NULL |
    | 192.168.7.50 | 3306 | 1504590283795083 | 521 | NULL |
    | 118.190.67.67 | 3306 | 1504590283779977 | 68310 | NULL |
    | 192.168.7.50 | 3306 | 1504590223794987 | 533 | NULL |
    | 118.190.67.67 | 3306 | 1504590223779913 | 53220 | NULL |
    | 192.168.7.50 | 3306 | 1504590163794887 | 497 | NULL |
    | 118.190.67.67 | 3306 | 1504590163779772 | 71389 | NULL |
    | 192.168.7.50 | 3306 | 1504590103794788 | 487 | NULL |
    | 118.190.67.67 | 3306 | 1504590103779728 | 68372 | NULL |
    +---------------+------+------------------+-------------------------+---------------+
    10 rows in set (0.00 sec)
  • 查询ping日志:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    MySQL [(none)] 13:46:22 > SELECT * FROM monitor.mysql_server_ping_log ORDER BY time_start_us DESC LIMIT 10;
    +---------------+------+------------------+----------------------+------------+
    | hostname | port | time_start_us | ping_success_time_us | ping_error |
    +---------------+------+------------------+----------------------+------------+
    | 192.168.7.50 | 3306 | 1504590413773092 | 100 | NULL |
    | 118.190.67.67 | 3306 | 1504590413770521 | 23105 | NULL |
    | 192.168.7.50 | 3306 | 1504590403773088 | 168 | NULL |
    | 118.190.67.67 | 3306 | 1504590403770479 | 23080 | NULL |
    | 192.168.7.50 | 3306 | 1504590393772977 | 135 | NULL |
    | 118.190.67.67 | 3306 | 1504590393770364 | 23078 | NULL |
    | 192.168.7.50 | 3306 | 1504590383772899 | 138 | NULL |
    | 118.190.67.67 | 3306 | 1504590383770309 | 23205 | NULL |
    | 192.168.7.50 | 3306 | 1504590373772885 | 102 | NULL |
    | 118.190.67.67 | 3306 | 1504590373770291 | 23099 | NULL |
    +---------------+------+------------------+----------------------+------------+
    10 rows in set (0.00 sec)
  • 查询后端DB状态

    1
    2
    3
    4
    5
    6
    7
    8
    MySQL [(none)] 13:46:55 > SELECT * FROM mysql_servers;
    +--------------+---------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------------+
    | hostgroup_id | hostname | port | status | weight | compression | max_connections | max_replication_lag | use_ssl | max_latency_ms | comment |
    +--------------+---------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------------+
    | 100 | 118.190.67.67 | 3306 | ONLINE | 1 | 0 | 1000 | 0 | 0 | 0 | db0,ReadWrite |
    | 101 | 192.168.7.50 | 3306 | ONLINE | 9 | 0 | 1000 | 0 | 0 | 0 | db0,ReadOnly |
    +--------------+---------------+------+--------+--------+-------------+-----------------+---------------------+---------+----------------+---------------+
    2 rows in set (0.00 sec)
  • 查询监控状态:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    MySQL [(none)] 13:47:56 > SELECT * FROM monitor.mysql_server_read_only_log ORDER BY time_start_us DESC LIMIT 10;   
    +---------------+------+------------------+-----------------+-----------+-------+
    | hostname | port | time_start_us | success_time_us | read_only | error |
    +---------------+------+------------------+-----------------+-----------+-------+
    | 192.168.7.50 | 3306 | 1504590524103828 | 577 | 1 | NULL |
    | 118.190.67.67 | 3306 | 1504590524103406 | 23499 | 0 | NULL |
    | 192.168.7.50 | 3306 | 1504590522603717 | 646 | 1 | NULL |
    | 118.190.67.67 | 3306 | 1504590522603268 | 23487 | 0 | NULL |
    | 192.168.7.50 | 3306 | 1504590521103641 | 629 | 1 | NULL |
    | 118.190.67.67 | 3306 | 1504590521103182 | 23497 | 0 | NULL |
    | 192.168.7.50 | 3306 | 1504590519603662 | 639 | 1 | NULL |
    | 118.190.67.67 | 3306 | 1504590519603201 | 23525 | 0 | NULL |
    | 192.168.7.50 | 3306 | 1504590518103492 | 618 | 1 | NULL |
    | 118.190.67.67 | 3306 | 1504590518103062 | 23508 | 0 | NULL |
    +---------------+------+------------------+-----------------+-----------+-------+
    10 rows in set (0.00 sec)
  • 查询用户信息:

    1
    2
    3
    4
    5
    6
    7
    MySQL [(none)] 13:49:28 >  SELECT * FROM mysql_users;   
    +----------+-----------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+
    | username | password | active | use_ssl | default_hostgroup | default_schema | schema_locked | transaction_persistent | fast_forward | backend | frontend | max_connections |
    +----------+-----------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+
    | user0 | password0 | 1 | 0 | 100 | NULL | 0 | 1 | 0 | 1 | 1 | 1000 |
    +----------+-----------+--------+---------+-------------------+----------------+---------------+------------------------+--------------+---------+----------+-----------------+
    1 row in set (0.00 sec)
  • 查询连接池:

    1
    2
    3
    4
    5
    6
    7
    8
    MySQL [(none)] 13:51:19 > SELECT * FROM stats.stats_mysql_connection_pool;
    +-----------+---------------+----------+--------+----------+----------+--------+---------+---------+-----------------+-----------------+------------+
    | hostgroup | srv_host | srv_port | status | ConnUsed | ConnFree | ConnOK | ConnERR | Queries | Bytes_data_sent | Bytes_data_recv | Latency_us |
    +-----------+---------------+----------+--------+----------+----------+--------+---------+---------+-----------------+-----------------+------------+
    | 100 | 118.190.67.67 | 3306 | ONLINE | 0 | 1 | 1 | 0 | 3 | 58 | 233 | 23059 |
    | 101 | 192.168.7.50 | 3306 | ONLINE | 0 | 1 | 3 | 88 | 5 | 106 | 360 | 102 |
    +-----------+---------------+----------+--------+----------+----------+--------+---------+---------+-----------------+-----------------+------------+
    2 rows in set (0.00 sec)
  • 查询执行命令统计信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    MySQL [(none)] 13:51:47 > SELECT * FROM stats_mysql_commands_counters WHERE Total_cnt;
    +---------+---------------+-----------+-----------+-----------+---------+---------+----------+----------+-----------+-----------+--------+--------+---------+----------+
    | Command | Total_Time_us | Total_cnt | cnt_100us | cnt_500us | cnt_1ms | cnt_5ms | cnt_10ms | cnt_50ms | cnt_100ms | cnt_500ms | cnt_1s | cnt_5s | cnt_10s | cnt_INFs |
    +---------+---------------+-----------+-----------+-----------+---------+---------+----------+----------+-----------+-----------+--------+--------+---------+----------+
    | SELECT | 167664 | 27 | 15 | 4 | 0 | 6 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
    | SET | 0 | 2 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
    | SHOW | 13818 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
    +---------+---------------+-----------+-----------+-----------+---------+---------+----------+----------+-----------+-----------+--------+--------+---------+----------+
    3 rows in set (0.00 sec)
  • 查询路由规则的详情:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    MySQL [(none)] 13:53:05 > SELECT * FROM stats_mysql_query_digest ORDER BY sum_time DESC;
    +-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
    | hostgroup | schemaname | username | digest | digest_text | count_star | first_seen | last_seen | sum_time | min_time | max_time |
    +-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
    | 100 | information_schema | user0 | 0xA322907BCBC120DD | select * from tables limit ? | 1 | 1504589288 | 1504589288 | 133826 | 133826 | 133826 |
    | 100 | information_schema | user0 | 0x02033E45904D3DF0 | show databases | 1 | 1504589886 | 1504589886 | 13818 | 13818 | 13818 |
    | 100 | information_schema | user0 | 0x3765930C7143F468 | select * from t1 | 1 | 1504589311 | 1504589311 | 13466 | 13466 | 13466 |
    | 101 | information_schema | user0 | 0xA322907BCBC120DD | select * from tables limit ? | 2 | 1504582304 | 1504583327 | 7325 | 3564 | 3761 |
    | 101 | test | user0 | 0x814EDBB68FBACD5D | select * from neworders limit ? | 2 | 1504583184 | 1504583242 | 5959 | 2964 | 2995 |
    | 101 | test | user0 | 0x3765930C7143F468 | select * from t1 | 3 | 1504583288 | 1504589859 | 3386 | 64 | 3056 |
    | 101 | test | user0 | 0xA322907BCBC120DD | select * from tables limit ? | 2 | 1504583168 | 1504583299 | 3095 | 117 | 2978 |
    | 101 | information_schema | user0 | 0x620B328FE9D6D71A | SELECT DATABASE() | 2 | 1504589729 | 1504589859 | 607 | 193 | 414 |
    | 100 | information_schema | user0 | 0x226CD90D52A2BA0B | select @@version_comment limit ? | 8 | 1504582304 | 1504589886 | 0 | 0 | 0 |
    | 100 | test | user0 | 0x52B8B04283B3A18D | set names utf8 | 1 | 1504583162 | 1504583162 | 0 | 0 | 0 |
    | 100 | information_schema | user0 | 0x52B8B04283B3A18D | set names utf8 | 1 | 1504582582 | 1504582582 | 0 | 0 | 0 |
    | 100 | test | user0 | 0x226CD90D52A2BA0B | select @@version_comment limit ? | 6 | 1504583162 | 1504583299 | 0 | 0 | 0 |
    +-----------+--------------------+----------+--------------------+----------------------------------+------------+------------+------------+----------+----------+----------+
    12 rows in set (0.00 sec)

    MySQL [(none)] 13:55:46 > SELECT hostgroup hg, sum_time, count_star, digest_text FROM stats_mysql_query_digest ORDER BY sum_time DESC;
    +-----+----------+------------+----------------------------------+
    | hg | sum_time | count_star | digest_text |
    +-----+----------+------------+----------------------------------+
    | 100 | 133826 | 1 | select * from tables limit ? |
    | 100 | 13818 | 1 | show databases |
    | 100 | 13466 | 1 | select * from t1 |
    | 101 | 7325 | 2 | select * from tables limit ? |
    | 101 | 5959 | 2 | select * from neworders limit ? |
    | 101 | 3386 | 3 | select * from t1 |
    | 101 | 3095 | 2 | select * from tables limit ? |
    | 101 | 607 | 2 | SELECT DATABASE() |
    | 100 | 0 | 8 | select @@version_comment limit ? |
    | 100 | 0 | 1 | set names utf8 |
    | 100 | 0 | 1 | set names utf8 |
    | 100 | 0 | 6 | select @@version_comment limit ? |
    +-----+----------+------------+----------------------------------+
    12 rows in set (0.00 sec)
  • 查询路由规则:

    1
    2
    3
    4
    5
    6
    7
    8
    MySQL [(none)] 13:57:12 > SELECT rule_id, match_digest, match_pattern, replace_pattern, cache_ttl, apply FROM mysql_query_rules ORDER BY rule_id;
    +---------+--------------+-----------------------+-----------------+-----------+-------+
    | rule_id | match_digest | match_pattern | replace_pattern | cache_ttl | apply |
    +---------+--------------+-----------------------+-----------------+-----------+-------+
    | 10 | NULL | ^SELECT .* FOR UPDATE | NULL | NULL | 0 |
    | 11 | NULL | ^SELECT .* | NULL | NULL | 0 |
    +---------+--------------+-----------------------+-----------------+-----------+-------+
    2 rows in set (0.00 sec)
  • 参考:

  • https://github.com/sysown/proxysql/wiki/ProxySQL-Configuration#p6033
  • http://seanlook.com/2017/04/17/mysql-proxysql-route-rw_split/
  • http://severalnines.com/blog/how-proxysql-adds-failover-and-query-control-your-mysql-replication-setup

ProxySQL监控

PMM是Percona推出的一款很好的监控MySQL和MongoDB的开源工具,安装方便,功能丰富,图表美观,同时也支持ProxySQL的监控,故选择PMM作为ProxySQL的监控软件。
这里以ProxySQL服务端(192.168.7.50)为例,作为PMM的客户端,PMM服务器为139.196.99.230,仅演示ProxySQL安装PMM客户端,服务器安装配置省略。

PMM Client安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@jiessie ~]# yum localinstall -y /hwdata/duanwenjie/test/proxysql-1.4.1-1-centos67.x86_64.rpm 
Loaded plugins: security
Setting up Local Package Process
Examining /hwdata/duanwenjie/test/proxysql-1.4.1-1-centos67.x86_64.rpm: proxysql-1.4.1-1.x86_64
Marking /hwdata/duanwenjie/test/proxysql-1.4.1-1-centos67.x86_64.rpm to be installed
Resolving Dependencies
There are unfinished transactions remaining. You might consider running yum-complete-transaction first to finish them.
--> Running transaction check
---> Package proxysql.x86_64 0:1.4.1-1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

========================================================================================================================================================================
Package Arch Version Repository Size
========================================================================================================================================================================
Installing:
proxysql x86_64 1.4.1-1 /proxysql-1.4.1-1-centos67.x86_64 19 M

Transaction Summary
========================================================================================================================================================================
Install 1 Package(s)

Total size: 19 M
Installed size: 19 M
Downloading Packages:
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
Warning: RPMDB altered outside of yum.
** Found 1 pre-existing rpmdb problem(s), 'yum check' output follows:
mysql-community-libs-5.7.16-1.el6.x86_64 has missing requires of mysql-community-common(x86-64) >= ('0', '5.7.9', None)
Installing : proxysql-1.4.1-1.x86_64 1/1
Verifying : proxysql-1.4.1-1.x86_64 1/1

Installed:
proxysql.x86_64 0:1.4.1-1

Complete!
[root@jiessie ~]#

PMM Client配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@jiessie ~]# ifconfig  #查看IP地址
eth0 Link encap:Ethernet HWaddr 00:16:3E:00:DC:49
inet addr:192.168.7.50 Bcast:192.168.15.255 Mask:255.255.240.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:1017660 errors:0 dropped:0 overruns:0 frame:0
TX packets:1380639 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:273791594 (261.1 MiB) TX bytes:116287303 (110.9 MiB)

lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:1297762 errors:0 dropped:0 overruns:0 frame:0
TX packets:1297762 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:141606631 (135.0 MiB) TX bytes:141606631 (135.0 MiB)

[root@jiessie ~]# pmm-admin config --bind-address 192.168.7.50 --client-address 139.196.95.103 --server 139.196.99.230:8080 --client-name ProxySQL_Test #添加config
OK, PMM server is alive.

PMM Server | 139.196.99.230:8080
Client Name | ProxySQL_Test
Client Address | 139.196.95.103 (192.168.7.50)
[root@jiessie ~]# pmm-admin add linux:metrics --force ProxySQL_Test #添加Linux监控
OK, now monitoring this system.
[root@jiessie ~]#
[root@jiessie ~]# pmm-admin add proxysql:metrics --dsn "admin:admin@tcp(127.0.0.1:6032)/" proxysql6032 #添加ProxySQL监控
OK, now monitoring ProxySQL metrics using DSN admin:***@tcp(localhost:6032)
[root@jiessie ~]#
[root@jiessie ~]# pmm-admin list #查看状态
pmm-admin 1.3.0

PMM Server | 139.196.99.230:8080
Client Name | ProxySQL_Test
Client Address | 139.196.95.103 (192.168.7.50)
Service Manager | unix-systemv

----------------- -------------- ----------- -------- ------------------------------ --------
SERVICE TYPE NAME LOCAL PORT RUNNING DATA SOURCE OPTIONS
----------------- -------------- ----------- -------- ------------------------------ --------
linux:metrics ProxySQL_Test 42000 YES -
mysql:metrics ProxySQL_Test 42002 YES root:***@tcp(127.0.0.1:3306)
proxysql:metrics proxysql6032 42004 YES admin:***@tcp(127.0.0.1:6032)

[root@jiessie ~]#

PMM Server监控展示

PMM Server监控项包括:

  • 客户端连接数
  • 客户端总查询
  • ProxySQL连接池状态
  • 活动连接
  • 失败连接
  • 客户端查询路由详情
  • 客户端延迟状态
  • 网络接口
    image

总结

ProxySQL是一款很出色的MySQL中间件,在稳定性上、易用性、高性能等方面表现很不错。由于发布的时间较短,功能可能还不太完善,需要多做测试,特别是查询路由和规则方面需要详情的了解,测试。可重点关注。

误删除ibdata1(Table doesn,t exist)恢复案例

发表于 2017-07-26 | 分类于 备份恢复
字数统计: | 阅读时长 ≈

前言

MySQL从5.5版本开始,InnoDB存储引擎已经成为默认存储引擎。从物理文件的结构来看,InnoDB包含ibdata1,ib_logfilexx,数据文件等其他日志文件;其中参数innodb_file_per_table用于设置是否开启独立表空间,当开启独立表空间时数据和索引会存在于ibd文件中,当未开启独立表空间时,此时使用的是共享表空间,数据和索引会存储在ibdata1文件中。如果在清理空间时不小心把ibdata1文件删除了,未开启独立表空间时,数据也会被删除,只能从备份中恢复,没有其他办法。如果开启了独立表空间,可从ibd文件中恢复数据。本文将介绍在开启独立表空间一些恢复案例,前提是无备份。

准备

MySQL版本:percona server 5.6.36

案例步骤

  • 启动MySQL实例
  • 插入数据,中间穿插创建删除表,操持ibdata1内部tablespace的ID真实性
  • 模拟删除ibdata1和ib_logfile文件
  • 保存数据文件和表结构文件到其他目录
  • 提取该实例的所有表结构
  • 通过提取的表结构,创建实例下的所有表
  • 通过ALTER TABLE dbName.tableName DISCARD TABLESPACE删除新创建表的ibd文件
  • 拷贝原数据文件到对应的ibd文件目录
  • 修改ibd文件权限
  • 通过ALTER TABLE dbName.tableName IMPORT TABLESPACE导入拷贝的ibd文件
  • 使用mysqldump导出数据
  • 重建实例,导入数据

原理分析

  • 恢复的步骤中,其中修改InnoDB表的tablespace ID最为重要。默认情况下,当开启独立表空间时,即使表数据文件和索引存在于数据目录中,但是每个表都有一个表的空间ID做标识,这部分标识同样存在于ibdata1中,是一个关联的关系。当两者不一致时,数据字典里找不到表,InnoDB引擎就无法加载数据目录下的ibd文件。恢复的目的使两者保持一致,正常加载数据。
  • ibdata1内部结构
    image
  • ibd内部结构
    image
  • 图片来源于Jeremy Cole,原理写的很清楚,请参考。
  • ibdata1中的insert buffer/double buffer等可以不用关心,本次恢复用不到。

案例

启动MySQL实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
[root@iZuf6c08fdv8duubho2b0rZ ~]# ll /hwdata/data/percona
总用量 1341500
-rw-rw---- 1 mysql mysql 56 7月 4 23:50 auto.cnf
-rw-rw---- 1 mysql mysql 1073741824 7月 27 15:44 ibdata1
-rw-rw---- 1 mysql mysql 134217728 7月 27 15:44 ib_logfile0
-rw-rw---- 1 mysql mysql 134217728 7月 27 15:44 ib_logfile1
drwx------ 2 mysql mysql 4096 6月 20 13:43 mysql
-rw-rw---- 1 mysql mysql 151 7月 27 15:44 mysql-bin.000001
-rw-rw---- 1 mysql mysql 19 7月 27 15:44 mysql-bin.index
-rw-rw---- 1 mysql mysql 26901 7月 27 15:44 mysql-error.log
-rw-rw---- 1 mysql mysql 5 7月 27 15:44 mysql.pid
-rw-rw---- 1 mysql mysql 1037 7月 27 15:44 mysql-slow.log
drwx------ 2 mysql mysql 4096 6月 20 13:43 performance_schema
-rw-rw---- 1 mysql mysql 10485760 7月 27 15:44 undo001
-rw-rw---- 1 mysql mysql 10485760 7月 27 15:44 undo002
-rw-rw---- 1 mysql mysql 10485760 7月 27 15:44 undo003
[root@iZuf6c08fdv8duubho2b0rZ ~]# /etc/init.d/mysqld stop
Shutting down MySQL (Percona Server).. [确定]
[root@iZuf6c08fdv8duubho2b0rZ ~]# rm -rf /hwdata/data/percona/ib* /hwdata/data/percona/undo00*
[root@iZuf6c08fdv8duubho2b0rZ ~]# ll /hwdata/data/percona
总用量 60
-rw-rw---- 1 mysql mysql 56 7月 4 23:50 auto.cnf
drwx------ 2 mysql mysql 4096 6月 20 13:43 mysql
-rw-rw---- 1 mysql mysql 174 7月 27 15:45 mysql-bin.000001
-rw-rw---- 1 mysql mysql 19 7月 27 15:44 mysql-bin.index
-rw-rw---- 1 mysql mysql 31528 7月 27 15:45 mysql-error.log
-rw-rw---- 1 mysql mysql 1037 7月 27 15:44 mysql-slow.log
drwx------ 2 mysql mysql 4096 6月 20 13:43 performance_schema
[root@iZuf6c08fdv8duubho2b0rZ ~]# /etc/init.d/mysqld start
Starting MySQL (Percona Server)............ [确定]
[root@iZuf6c08fdv8duubho2b0rZ ~]#

插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@iZuf6c08fdv8duubho2b0rZ ~]# cat auto_insert_data.sh   #简单的插入脚本
#!/bin/bash
# Name: auto_insert_data
# Author: jiessie
# Describe: auto insert data
# Date: 20170727

# basic variables
MySQL_CMD=/usr/local/mysql/bin/mysql
MySQL_User=root
MySQL_Pass=123456
MySQL_Host=localhost
MySQL_Db=test1

# create database
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass -e "create database if not exists $MySQL_Db;"

# insert data
for ((i=1;i<=10;i++));
do
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass $MySQL_Db -e "create table employee$i(id int(10) unsigned NOT NULL AUTO_INCREMENT,empname varchar(64) NOT NULL DEFAULT '',PRIMARY KEY (id));"
for((j=1;j<=1000;j++));
do
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass $MySQL_Db -e "insert into employee$i values(null,'employee$j');"
done
# create other table and delete
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass $MySQL_Db -e "create table tmp$i(id int);drop table tmp$i;"
done


[root@iZuf6c08fdv8duubho2b0rZ ~]# nohup ./auto_insert_data.sh & #执行插入数据
[1] 4562
[root@iZuf6c08fdv8duubho2b0rZ ~]# nohup: 忽略输入并把输出追加到"nohup.out"

[root@iZuf6c08fdv8duubho2b0rZ ~]#

查看数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@iZuf6c08fdv8duubho2b0rZ ~]# mysql -uroot -p123456 test1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 10202
Server version: 5.6.36-82.0-log Source distribution

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [test1] 17:04:40 > show tables;
+-----------------+
| Tables_in_test1 |
+-----------------+
| employee1 |
| employee10 |
| employee2 |
| employee3 |
| employee4 |
| employee5 |
| employee6 |
| employee7 |
| employee8 |
| employee9 |
+-----------------+
10 rows in set (0.00 sec)

MySQL [test1] 17:04:42 > select count(*) from employee1;
+----------+
| count(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.00 sec)

MySQL [test1] 17:04:44 > exit
Bye
[root@iZuf6c08fdv8duubho2b0rZ ~]#

删除ibdata1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
[root@iZuf6c08fdv8duubho2b0rZ ~]# cd /hwdata/data/percona
[root@iZuf6c08fdv8duubho2b0rZ percona]# ll
总用量 1345228
-rw-rw---- 1 mysql mysql 56 7月 4 23:50 auto.cnf
-rw-rw---- 1 mysql mysql 14522 7月 27 17:02 ib_buffer_pool
-rw-rw---- 1 mysql mysql 1073741824 7月 27 17:03 ibdata1
-rw-rw---- 1 mysql mysql 134217728 7月 27 17:03 ib_logfile0
-rw-rw---- 1 mysql mysql 134217728 7月 27 17:02 ib_logfile1
drwx------ 2 mysql mysql 4096 6月 20 13:43 mysql
-rw-rw---- 1 mysql mysql 3325813 7月 27 17:03 mysql-bin.000001
-rw-rw---- 1 mysql mysql 19 7月 27 17:02 mysql-bin.index
-rw-rw---- 1 mysql mysql 496222 7月 27 17:05 mysql-error.log
-rw-rw---- 1 mysql mysql 5 7月 27 17:02 mysql.pid
-rw-rw---- 1 mysql mysql 2383 7月 27 17:04 mysql-slow.log
drwx------ 2 mysql mysql 4096 6月 20 13:43 performance_schema
drwx------ 2 mysql mysql 4096 7月 27 17:03 test1
-rw-rw---- 1 mysql mysql 10485760 7月 27 17:03 undo001
-rw-rw---- 1 mysql mysql 10485760 7月 27 17:03 undo002
-rw-rw---- 1 mysql mysql 10485760 7月 27 17:03 undo003
[root@iZuf6c08fdv8duubho2b0rZ percona]# rm -rf ib_buffer_pool ib_logfile* undo00* ibdata1 #删除ibdata1相关信息
[root@iZuf6c08fdv8duubho2b0rZ percona]# /etc/init.d/mysqld restart #重启服务
Shutting down MySQL (Percona Server).. [确定]
Starting MySQL (Percona Server)............ [确定]
[root@iZuf6c08fdv8duubho2b0rZ percona]# mysql -uroot -p123456 test1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.6.36-82.0-log Source distribution

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [test1] 17:08:29 > select count(*) from employee1; #提示表不存在
ERROR 1146 (42S02): Table 'test1.employee1' doesn't exist
MySQL [test1] 17:08:32 > exit
Bye
[root@iZuf6c08fdv8duubho2b0rZ percona]# ll test1/ #查看frm和ibd文件存在
总用量 1404
-rw-rw---- 1 mysql mysql 59 7月 27 17:03 db.opt
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee10.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee10.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee1.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee1.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee2.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee2.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee3.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee3.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee4.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee4.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee5.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee5.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee6.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee6.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee7.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee7.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee8.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee8.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee9.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 e
yee9.ibd
[root@iZuf6c08fdv8duubho2b0rZ percona]#

保存数据文件

1
2
3
4
[root@iZuf6c08fdv8duubho2b0rZ percona]# cp -a test1/ /usr/src/  #数据暂时保存到/usr/src目录下
[root@iZuf6c08fdv8duubho2b0rZ percona]# ll /usr/src/test1 |wc -l
22
[root@iZuf6c08fdv8duubho2b0rZ percona]#

恢复表结构

如果表结构保存的有,不需要此步骤,直接按照表结构重新创建表
此步骤使用官方工具mysql-utilities通过frm文件提取表结构,地址:

mysql-utilities原理

  • 默认以再生实例启动,读取frm文件,再生实例关闭,清理临时文件
  • 另一个模式是诊断模式,需要指定 –diagnostic 选项。byte-by-byte读取.frm文件,该模式有更多的局限性,不能校验字符集
  • 请参考官网:

注意事项

  • 某些引擎表在默认模式下不可读取的。如PARTITION, PERFORMANCE_SCHEMA,必需在诊断模式下可读。
  • 要在创建语句中改变存储引擎,可使用–new-storage-engine 选项。如果有指定该选项,同时必须指定–frmdir选项,该工具生成新的.frm文件,前缀为new_,并保存在–frmdir目录下。
  • 关掉所有信息除了CREATE 语句和警告或错误信息,使用–quiet选项。
  • 使用–show-stats 选项统计每个.frm文件信息。
  • 使用–user 选项指定再生的实例以哪个权限运行。
  • 如果再生的实例超过10秒启动,需调大–start-timeout 选项参数。

mysql-utilities安装

由于yum安装的是1.3的版本,1.6有bug,使用1.5.6版本,故采用源码包的形式安装,安装过程中如报错,请先安装驱动程序Connector/Python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@iZuf6c08fdv8duubho2b0rZ ~]# cd /usr/src/
[root@iZuf6c08fdv8duubho2b0rZ src]# wget https://dev.mysql.com/get/Downloads/MySQLGUITools/mysql-utilities-1.5.6.tar.gz
[root@iZuf6c08fdv8duubho2b0rZ src]# tar -zxf mysql-utilities-1.5.6.tar.gz
[root@iZuf6c08fdv8duubho2b0rZ src]# cd mysql-utilities-1.5.6
[root@iZuf6c08fdv8duubho2b0rZ mysql-utilities-1.5.6]#
[root@iZuf6c08fdv8duubho2b0rZ mysql-utilities-1.5.6]# python ./setup.py build #编译,过程省略
[root@iZuf6c08fdv8duubho2b0rZ mysql-utilities-1.5.6]# python ./setup.py install #安装,过程省略
[root@iZuf6c08fdv8duubho2b0rZ mysql-utilities-1.5.6]# ll /usr/bin/mysql* -t #以下为生成的可执行文件
-rwxr-xr-x 1 root root 11966 7月 27 11:50 /usr/bin/mysqlauditadmin
-rwxr-xr-x 1 root root 12217 7月 27 11:50 /usr/bin/mysqlauditgrep
-rwxr-xr-x 1 root root 16680 7月 27 11:50 /usr/bin/mysqldbcompare
-rwxr-xr-x 1 root root 13918 7月 27 11:50 /usr/bin/mysqldbcopy
-rwxr-xr-x 1 root root 14815 7月 27 11:50 /usr/bin/mysqldbexport
-rwxr-xr-x 1 root root 13605 7月 27 11:50 /usr/bin/mysqldbimport
-rwxr-xr-x 1 root root 10722 7月 27 11:50 /usr/bin/mysqldiff
-rwxr-xr-x 1 root root 7385 7月 27 11:50 /usr/bin/mysqldiskusage
-rwxr-xr-x 1 root root 14197 7月 27 11:50 /usr/bin/mysqlfabric
-rwxr-xr-x 1 root root 15457 7月 27 11:50 /usr/bin/mysqlfailover
-rwxr-xr-x 1 root root 18222 7月 27 11:50 /usr/bin/mysqlfrm
-rwxr-xr-x 1 root root 6251 7月 27 11:50 /usr/bin/mysqlindexcheck
-rwxr-xr-x 1 root root 5356 7月 27 11:50 /usr/bin/mysqlmetagrep
-rwxr-xr-x 1 root root 5984 7月 27 11:50 /usr/bin/mysqlprocgrep
-rwxr-xr-x 1 root root 7694 7月 27 11:50 /usr/bin/mysqlreplicate
-rwxr-xr-x 1 root root 16669 7月 27 11:50 /usr/bin/mysqlrpladmin
-rwxr-xr-x 1 root root 6407 7月 27 11:50 /usr/bin/mysqlrplcheck
-rwxr-xr-x 1 root root 15542 7月 27 11:50 /usr/bin/mysqlrplms
-rwxr-xr-x 1 root root 6695 7月 27 11:50 /usr/bin/mysqlrplshow
-rwxr-xr-x 1 root root 11489 7月 27 11:50 /usr/bin/mysqlrplsync
-rwxr-xr-x 1 root root 8642 7月 27 11:50 /usr/bin/mysqlserverclone
-rwxr-xr-x 1 root root 5945 7月 27 11:50 /usr/bin/mysqlserverinfo
-rwxr-xr-x 1 root root 6923 7月 27 11:50 /usr/bin/mysqluc
-rwxr-xr-x 1 root root 8048 7月 27 11:50 /usr/bin/mysqluserclone

以上为生成的可执行文件中,我们只需要用到mysqlfrm,用于表结构恢复

开始提取frm表结构

此用法启动了3333的实例,读取hwdata/data/percona/test1/下的所有frm文件,没有指定数据库,生成了以最后一个文件夹为DB名字的表结构

1
2
3
4
5
6
7
8
9
[root@iZuf6c08fdv8duubho2b0rZ percona]# mysqlfrm --basedir=/usr/local/mysql --port=3333 --user=mysql /hwdata/data/percona/test1/ > table_frm.sql
[root@iZuf6c08fdv8duubho2b0rZ percona]# mysqlfrm --help #其他使用方法请参考帮助
[root@iZuf6c08fdv8duubho2b0rZ percona]# sed '/^#/d;/^$/d' table_frm.sql |head -5 #如下显示
CREATE TABLE `test1`.`employee1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`empname` varchar(64) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk
[root@iZuf6c08fdv8duubho2b0rZ percona]# sed -i 's@CHARSET=gbk@CHARSET=gbk;@g' table_frm.sql #由于导出后没有以分号结尾,此命令处理添加上分号

导入frm表结构

确认数据文件已经保存至其他目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
[root@iZuf6c08fdv8duubho2b0rZ percona]# ll /usr/src/test1/      #确认下数据文件是否保存在此
总用量 1404
-rw-rw---- 1 mysql mysql 59 7月 27 17:03 db.opt
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee10.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee10.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee1.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee1.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee2.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee2.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee3.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee3.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee4.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee4.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee5.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee5.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee6.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee6.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee7.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee7.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee8.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee8.ibd
-rw-rw---- 1 mysql mysql 8592 7月 27 17:03 employee9.frm
-rw-rw---- 1 mysql mysql 131072 7月 27 17:03 employee9.ibd
[root@iZuf6c08fdv8duubho2b0rZ percona]# ll
总用量 1351956
-rw-rw---- 1 mysql mysql 56 7月 4 23:50 auto.cnf
-rw-r--r-- 1 root root 0 7月 27 17:08 exit
-rw-rw---- 1 mysql mysql 2563 7月 27 17:08 ib_buffer_pool
-rw-rw---- 1 mysql mysql 1073741824 7月 27 17:08 ibdata1
-rw-rw---- 1 mysql mysql 134217728 7月 27 17:08 ib_logfile0
-rw-rw---- 1 mysql mysql 134217728 7月 27 17:08 ib_logfile1
drwx------ 2 mysql mysql 4096 6月 20 13:43 mysql
-rw-rw---- 1 mysql mysql 3325836 7月 27 17:08 mysql-bin.000001
-rw-rw---- 1 mysql mysql 191 7月 27 17:08 mysql-bin.000002
-rw-rw---- 1 mysql mysql 38 7月 27 17:08 mysql-bin.index
-rw-rw---- 1 mysql mysql 7003893 7月 28 10:44 mysql-error.log
-rw-rw---- 1 mysql mysql 6 7月 27 17:08 mysql.pid
-rw-rw---- 1 mysql mysql 387620 7月 28 10:37 mysql-slow.log
drwx------ 2 mysql mysql 4096 6月 20 13:43 performance_schema
-rw-r--r-- 1 root root 0 7月 27 17:08 show
-rw-r--r-- 1 root root 3064 7月 28 10:42 table_frm.sql
drwx------ 2 mysql mysql 4096 7月 28 10:27 test1
-rw-rw---- 1 mysql mysql 10485760 7月 27 17:08 undo001
-rw-rw---- 1 mysql mysql 10485760 7月 27 17:08 undo002
w-rw---- 1 mysql mysql 10485760 7月 27 17:08 undo003
[root@iZuf6c08fdv8duubho2b0rZ percona]# rm -rf test1/*
[root@iZuf6c08fdv8duubho2b0rZ percona]# /etc/init.d/mysqld restart
Shutting down MySQL (Percona Server).. [确定]
Starting MySQL (Percona Server).. [确定]
[root@iZuf6c08fdv8duubho2b0rZ percona]# mysql -uroot -p123456 test1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.6.36-82.0-log Source distribution

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [test1] 10:47:30 > source /hwdata/data/percona/table_frm.sql;
Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.03 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.01 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.01 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.02 sec)

MySQL [test1] 10:47:31 > show tables; #新表已创建
+-----------------+
| Tables_in_test1 |
+-----------------+
| employee1 |
| employee10 |
| employee2 |
| employee3 |
| employee4 |
| employee5 |
| employee6 |
| employee7 |
| employee8 |
| employee9 |
+-----------------+
10 rows in set (0.00 sec)

MySQL [test1] 10:49:54 > select count(*) from employee1; #目录无数据
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (0.00 sec)

MySQL [test1] 10:50:01 >

删除新创建表的ibd文件

由于表比较多,通过脚本的形式去执行批量删除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
[root@iZuf6c08fdv8duubho2b0rZ ~]# cat auto_delete_tablespace.sh     #简单的批量删除表空间脚本
#!/bin/bash
# Name: auto_delete_tablespace
# Author: jiessie
# Describe: auto delete create table tablespace
# Date: 20170727

# basic variables
MySQL_CMD=/usr/local/mysql/bin/mysql
MySQL_User=root
MySQL_Pass=123456
MySQL_Host=localhost
MySQL_Db=test1
MySQL_Table_list=/tmp/table_list.sql

# generate table list and exec delete tablespace sql
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass $MySQL_Db -e "select concat('alter table $MySQL_Db.',table_name,' discard tablespace;') from information_schema.tables where table_schema='$MySQL_Db'" > $MySQL_Table_list
sed -i '1d' $MySQL_Table_list
[root@iZuf6c08fdv8duubho2b0rZ ~]# ./auto_delete_tablespace.sh #执行获取删除表空间脚本
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@iZuf6c08fdv8duubho2b0rZ ~]# cat /tmp/table_list.sql #查看生成的删除表空间的SQL语句
alter table test1.employee1 discard tablespace;
alter table test1.employee10 discard tablespace;
alter table test1.employee2 discard tablespace;
alter table test1.employee3 discard tablespace;
alter table test1.employee4 discard tablespace;
alter table test1.employee5 discard tablespace;
alter table test1.employee6 discard tablespace;
alter table test1.employee7 discard tablespace;
alter table test1.employee8 discard tablespace;
alter table test1.employee9 discard tablespace;
[root@iZuf6c08fdv8duubho2b0rZ ~]# ll /hwdata/data/percona/test1/*.ibd #查询新创建表后生成的ibd文件
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee10.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee1.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee2.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee3.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee4.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee5.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee6.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee7.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee8.ibd
-rw-rw---- 1 mysql mysql 98304 7月 28 10:47 /hwdata/data/percona/test1/employee9.ibd
[root@iZuf6c08fdv8duubho2b0rZ ~]# mysql -uroot -p123456 test1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1753
Server version: 5.6.36-82.0-log Source distribution

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [test1] 11:10:40 > source /tmp/table_list.sql; #执行删除表空间
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

MySQL [test1] 11:10:46 > exit
Bye
[root@iZuf6c08fdv8duubho2b0rZ ~]# ll /hwdata/data/percona/test1/*.ibd #验证表空间已经删除
ls: 无法访问/hwdata/data/percona/test1/*.ibd: 没有那个文件或目录
[root@iZuf6c08fdv8duubho2b0rZ ~]#

导入原数据ibd文件

由于原表过多,通过脚本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@iZuf6c08fdv8duubho2b0rZ ~]# cat auto_recovery_data.sh         #恢复ibd文件脚本
#!/bin/bash
# Name: auto_delete_tablespace
# Author: jiessie
# Describe: auto delete create table tablespace
# Date: 20170727

# basic variables
MySQL_CMD=/usr/local/mysql/bin/mysql
MySQL_User=root
MySQL_Pass=123456
MySQL_Host=localhost
MySQL_Db=test1
MySQL_Ibd_filepath=/usr/src/test1/
MySQL_Table_list=/tmp/tables.sql
MySQL_Table_path=/hwdata/data/percona/test1

# import ibd file
/etc/init.d/mysqld stop
cd $MySQL_Ibd_filepath && cp -a * $MySQL_Table_path
chown -R mysql:mysql $MySQL_Table_path
/etc/init.d/mysqld start
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass $MySQL_Db -e "select table_name from information_schema.tables where table_schema='$MySQL_Db'" > $MySQL_Table_list
sed -i '1d' $MySQL_Table_list
cat $MySQL_Table_list | while read line
do
$MySQL_CMD -h$MySQL_Host -u$MySQL_User -p$MySQL_Pass $MySQL_Db -e "alter table $MySQL_Db.$line import tablespace;"
done
[root@iZuf6c08fdv8duubho2b0rZ ~]# ./auto_recovery_data.sh #执行恢复脚本
Shutting down MySQL (Percona Server)... [确定]
Starting MySQL (Percona Server).. [确定]
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
mysql: [Warning] Using a password on the command line interface can be insecure.
[root@iZuf6c08fdv8duubho2b0rZ ~]#

数据验证

通过count计数,比较数据是否恢复正常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[root@iZuf6c08fdv8duubho2b0rZ ~]# mysql -uroot -p123456 test1
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 704
Server version: 5.6.36-82.0-log Source distribution

Copyright (c) 2009-2017 Percona LLC and/or its affiliates
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [test1] 14:51:58 > select count(*) from employee1
-> union all
-> select count(*) from employee2
-> union all
-> select count(*) from employee3
-> union all
-> select count(*) from employee4
-> union all
-> select count(*) from employee5
-> union all
-> select count(*) from employee6
-> union all
-> select count(*) from employee7
-> union all
-> select count(*) from employee8
-> union all
-> select count(*) from employee9
-> union all
-> select count(*) from employee10;
+----------+
| count(*) |
+----------+
| 1000 |
| 1000 |
| 1000 |
| 1000 |
| 1000 |
| 1000 |
| 1000 |
| 1000 |
| 1000 |
| 1000 |
+----------+
10 rows in set (0.01 sec)

MySQL [test1] 14:52:04 >

mysqldump导出

1
2
3
4
5
[root@iZuf6c08fdv8duubho2b0rZ ~]# mysqldump -uroot -p123456 test1 --set-gtid-purged=OFF --opt -q --master-data=2 --single-transaction -R --events --triggers > test1_20170728.sql
Warning: Using a password on the command line interface can be insecure.
[root@iZuf6c08fdv8duubho2b0rZ ~]# ll test1_20170728.sql
-rw-r--r-- 1 root root 206957 7月 28 15:16 test1_20170728.sql
[root@iZuf6c08fdv8duubho2b0rZ ~]#

重启导入

可把test1库删除后,重启服务,重新创建test1库,把mysqldump出的文件再次导入。

总结

  • 像实验中案例,误删除ibdata1数据的经常会出现。
  • 但是在最新的5.6.36版本中,不管是当前实验案例,还是直接从其他实例拷贝frm和ibd文件,在当前实例中重新创建表,再discard掉ibd文件,最后再import ibd文件,竟然没有报表空间ID不一致的错误。以前在5.5版本中,确认是会报表空间ID不一致情况,不确定是不是5.6版本改进了这方面的功能。
  • 5.5版本版本恢复案例请参考(https://dbarobin.com/2016/04/23/ibd-recovery/),思路不错。
12
Jiessie

Jiessie

最怕一生碌碌无为,还说平凡难能可贵!

15 日志
7 分类
25 标签
RSS
Percona MariaDB serverteam highavailability 淘宝月报 dimitrik
© 2020 Jiessie
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4
博客全站共字