在 MySQL 配置主从复制

MySQL 基于 GTID 的主从复制配置教程

在 MySQL 配置主从复制

GTID 相比传统 binlog(基于 file + position)的复制方式,核心优势在于把复制定位从“物理位置”升级为“逻辑事务 ID”。在 binlog 模式下,从库需要依赖 binlog 文件名和执行位置来继续复制,一旦发生主从切换或故障恢复,就必须人工找到正确的日志位置,操作复杂且容易出错,还可能因为定位不准导致数据重复或遗漏。而 GTID 模式下,每个事务都有全局唯一的 ID,从库只需要记录自己已经执行过的 GTID 集合,主库发送事务时,从库会自动判断是否执行过该事务,未执行则执行,已执行则跳过,因此不再依赖 position,实现自动对齐复制进度。这使得主从切换更加简单、安全,恢复更自动化,同时也减少了人为操作带来的数据不一致风险,并且在复杂拓扑(如多源复制)下更容易管理和避免重复执行问题。

通过 Docker Compose 部署

 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
services:
  mysql-master:
    image: mysql:8.0
    container_name: mysql-master
    environment:
      MYSQL_DATABASE: db
      MYSQL_USER: user
      MYSQL_PASSWORD: pass
      MYSQL_ROOT_PASSWORD: root
    command: >
      --server-id=1
      --log-bin=mysql-bin
      --binlog-format=ROW
      --gtid-mode=ON
      --enforce-gtid-consistency=ON
      --log-slave-updates=ON
    ports:
      - "3306:3306"
    volumes:
      - mysql_master_data:/var/lib/mysql

  mysql-slave:
    image: mysql:8.0
    container_name: mysql-slave
    restart: always
    ports:
      - "3307:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
    command: >
      --server-id=2
      --relay-log=mysql-relay-bin
      --gtid-mode=ON
      --enforce-gtid-consistency=ON
      --log-slave-updates=ON
      --read-only=ON
    volumes:
      - mysql_slave_data:/var/lib/mysql

volumes:
  mysql_master_data:
  mysql_slave_data:

将以上 Docker Compose 文件保存至某个目录,例如 C:\Users\Admin\Desktop\docker 然后在当前目录下运行 docker compose up -d 命令启动,控制台输出如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
C:\Users\Admin\Desktop\docker>docker compose up -d

[+] up 3/3
 ✔ Network docker_default    Created                                                                                0.0s
 ✔ Volume docker_slave_data  Created                                                                                0.1s
[+] up 5/5docker_master_data Created                                                                                0.1s
 ✔ Network docker_default    Created                                                                                0.0s
 ✔ Volume docker_slave_data  Created                                                                                0.1s
 ✔ Volume docker_master_data Created                                                                                0.1s
 ✔ Container mysql-slave     Started                                                                                0.4s
 ✔ Container mysql-master    Started                                                                                0.4s

C:\Users\Admin\Desktop\docker>

通过 docker ps 命令查看容器是否正常运行:

1
2
3
4
5
6
C:\Users\Admin\Desktop\docker>docker ps
CONTAINER ID  IMAGE                        COMMAND               CREATED        STATUS        PORTS                              NAMES
c31150c279b5  docker.io/library/mysql:8.0  --server-id=2 --r...  3 minutes ago  Up 3 minutes  0.0.0.0:3307->3306/tcp, 33060/tcp  mysql-slave
43921bba8afd  docker.io/library/mysql:8.0  --server-id=1 --l...  3 minutes ago  Up 3 minutes  0.0.0.0:3306->3306/tcp, 33060/tcp  mysql-master

C:\Users\Admin\Desktop\docker>

控制台输出表示 MySQL 主从全部正常运行,接着通过命令进入容器 docker exec -it mysql-master mysql -uroot -proot ,进入后创建从机用户,

1
2
3
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;

同样通过命令 docker exec -it mysql-slave mysql -uroot -proot 进入从机,进行 GTID 主从配置,

1
2
3
4
5
6
CHANGE REPLICATION SOURCE TO
SOURCE_HOST='mysql-master',
SOURCE_USER='repl',
SOURCE_PASSWORD='repl',
SOURCE_AUTO_POSITION=1,
GET_SOURCE_PUBLIC_KEY=1; #MySQL 8.0 默认使用 caching_sha2_password 认证,不加 GET_SOURCE_PUBLIC_KEY=1 会报错

接着启动复制,

1
START REPLICA;

通过以下命令查看是否启动成功:

1
SHOW REPLICA STATUS\G;

输出如下:

 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
mysql> SHOW REPLICA STATUS\G;
*************************** 1. row ***************************
             Replica_IO_State: Waiting for source to send event
                  Source_Host: mysql-master
                  Source_User: repl
                  Source_Port: 3306
                Connect_Retry: 60
              Source_Log_File: mysql-bin.000003
          Read_Source_Log_Pos: 899
               Relay_Log_File: mysql-relay-bin.000003
                Relay_Log_Pos: 1115
        Relay_Source_Log_File: mysql-bin.000003
           Replica_IO_Running: Yes
          Replica_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_Source_Log_Pos: 899
              Relay_Log_Space: 3000043
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Source_SSL_Allowed: No
           Source_SSL_CA_File:
           Source_SSL_CA_Path:
              Source_SSL_Cert:
            Source_SSL_Cipher:
               Source_SSL_Key:
        Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Source_Server_Id: 1
                  Source_UUID: d1102c57-3a3c-11f1-aac5-6ad0b3b2c0b9
             Source_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
    Replica_SQL_Running_State: Replica has read all relay log; waiting for more updates
           Source_Retry_Count: 86400
                  Source_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Source_SSL_Crl:
           Source_SSL_Crlpath:
           Retrieved_Gtid_Set: d1102c57-3a3c-11f1-aac5-6ad0b3b2c0b9:1-11
            Executed_Gtid_Set: d0e5e05b-3a3c-11f1-a0a5-7622b0cdb3fc:1-5,
d1102c57-3a3c-11f1-aac5-6ad0b3b2c0b9:1-11
                Auto_Position: 1
         Replicate_Rewrite_DB:
                 Channel_Name:
           Source_TLS_Version:
       Source_public_key_path:
        Get_Source_public_key: 1
            Network_Namespace:
1 row in set (0.00 sec)

ERROR:
No query specified

出现以上提示 Replica_IO_Running: Yes Replica_SQL_Running: Yes 代表主从复制已经建立成功。

验证主从复制

第一步,在主库创建测试数据

docker exec -it mysql-master mysql -uroot -proot

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-- 创建测试数据库
CREATE DATABASE test_db;
USE test_db;

-- 创建测试表
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 插入数据
INSERT INTO users (name) VALUES ('张三'), ('李四'), ('王五');

第二步,在从库查询,确认数据已同步

docker exec -it mysql-slave mysql -uroot -proot

1
2
USE test_db;
SELECT * FROM users;

预期输出:

1
2
3
4
5
6
7
8
+----+------+---------------------+
| id | name | created_at          |
+----+------+---------------------+
|  1 |      | 2026-04-17 07:11:29 |
|  2 |      | 2026-04-17 07:11:29 |
|  3 |      | 2026-04-17 07:11:29 |
+----+------+---------------------+
3 rows in set (0.00 sec)

第三步,验证从库只读保护

先在从库开启 super-read-only

docker exec -it mysql-slave mysql -uroot -proot

1
SET GLOBAL super_read_only = ON;

使用非 root 用户登录进行测试,由于主从复制会将 user 也复制过来,所以直接用主库配置的 user 登录即可

docker exec -it mysql-slave mysql -uuser -ppass

1
INSERT INTO users (name) VALUES ('测试');

预期报错:

1
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement

问题排查

  1. 1. caching_sha2_password 认证失败 MySQL 8.0 默认使用 caching_sha2_password 插件,从库连接主库时需要在 CHANGE REPLICATION SOURCE TO 中加上 GET_SOURCE_PUBLIC_KEY=1,否则会报认证错误导致复制无法建立。
  2. root 用户可以绕过 read_only --read-only=ON 对拥有 SUPER 权限的用户(如 root)无效,生产环境必须使用 super_read_only,单独的 read_only 无法保护从库。
  3. Windows 终端中文乱码 Windows CMD 默认使用 GBK 编码,连接 MySQL 时需要加上 --default-character-set=utf8mb4 参数,或改用 DBeaver、Navicat 等 GUI 工具操作。
Licensed under CC BY-NC-SA 4.0