Java 项目实战: 外卖平台优化-从库Slave配置与主从复制验证

读写分离: 从库 Slave 配置与主从复制验证

纲要

本篇对应课程 day02 的「配置从库 Slave」与「主从复制测试」环节,完成复制链路的最后一环:

  • 修改从库配置 :只需设置 server-id=101(从库不产生 binlog 供他人复制,无需开 log-bin)
  • CHANGE MASTER TO :指定主库 IP、复制账号、binlog 文件名与位点
  • START SLAVE :启动复制线程;遇到 IO Thread 已运行时先 STOP SLAVE
  • SHOW SLAVE STATUS :确认 Slave_IO_Running 与 Slave_SQL_Running 双 Yes
  • 功能验证:主库建库 → 建表 → 插入 → 修改 → 删除,从库逐一验证同步

#mermaid-svg-bvJIrmZWqg5gINAm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bvJIrmZWqg5gINAm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bvJIrmZWqg5gINAm .error-icon{fill:#552222;}#mermaid-svg-bvJIrmZWqg5gINAm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bvJIrmZWqg5gINAm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bvJIrmZWqg5gINAm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bvJIrmZWqg5gINAm .marker.cross{stroke:#333333;}#mermaid-svg-bvJIrmZWqg5gINAm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bvJIrmZWqg5gINAm p{margin:0;}#mermaid-svg-bvJIrmZWqg5gINAm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bvJIrmZWqg5gINAm .cluster-label text{fill:#333;}#mermaid-svg-bvJIrmZWqg5gINAm .cluster-label span{color:#333;}#mermaid-svg-bvJIrmZWqg5gINAm .cluster-label span p{background-color:transparent;}#mermaid-svg-bvJIrmZWqg5gINAm .label text,#mermaid-svg-bvJIrmZWqg5gINAm span{fill:#333;color:#333;}#mermaid-svg-bvJIrmZWqg5gINAm .node rect,#mermaid-svg-bvJIrmZWqg5gINAm .node circle,#mermaid-svg-bvJIrmZWqg5gINAm .node ellipse,#mermaid-svg-bvJIrmZWqg5gINAm .node polygon,#mermaid-svg-bvJIrmZWqg5gINAm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bvJIrmZWqg5gINAm .rough-node .label text,#mermaid-svg-bvJIrmZWqg5gINAm .node .label text,#mermaid-svg-bvJIrmZWqg5gINAm .image-shape .label,#mermaid-svg-bvJIrmZWqg5gINAm .icon-shape .label{text-anchor:middle;}#mermaid-svg-bvJIrmZWqg5gINAm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bvJIrmZWqg5gINAm .rough-node .label,#mermaid-svg-bvJIrmZWqg5gINAm .node .label,#mermaid-svg-bvJIrmZWqg5gINAm .image-shape .label,#mermaid-svg-bvJIrmZWqg5gINAm .icon-shape .label{text-align:center;}#mermaid-svg-bvJIrmZWqg5gINAm .node.clickable{cursor:pointer;}#mermaid-svg-bvJIrmZWqg5gINAm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bvJIrmZWqg5gINAm .arrowheadPath{fill:#333333;}#mermaid-svg-bvJIrmZWqg5gINAm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bvJIrmZWqg5gINAm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bvJIrmZWqg5gINAm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bvJIrmZWqg5gINAm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bvJIrmZWqg5gINAm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bvJIrmZWqg5gINAm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bvJIrmZWqg5gINAm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bvJIrmZWqg5gINAm .cluster text{fill:#333;}#mermaid-svg-bvJIrmZWqg5gINAm .cluster span{color:#333;}#mermaid-svg-bvJIrmZWqg5gINAm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bvJIrmZWqg5gINAm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bvJIrmZWqg5gINAm rect.text{fill:none;stroke-width:0;}#mermaid-svg-bvJIrmZWqg5gINAm .icon-shape,#mermaid-svg-bvJIrmZWqg5gINAm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bvJIrmZWqg5gINAm .icon-shape p,#mermaid-svg-bvJIrmZWqg5gINAm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bvJIrmZWqg5gINAm .icon-shape .label rect,#mermaid-svg-bvJIrmZWqg5gINAm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bvJIrmZWqg5gINAm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bvJIrmZWqg5gINAm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bvJIrmZWqg5gINAm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
修改 /etc/my.cnf

server-id=101
systemctl restart mysqld
CHANGE MASTER TO

MASTER_HOST / USER / PASSWORD

MASTER_LOG_FILE / MASTER_LOG_POS
是否报错

slave 线程正在运行
STOP SLAVE
START SLAVE
SHOW SLAVE STATUS

确认双 Yes
测试:主库 DDL/DML

从库验证同步

一、第一步:修改从库配置

bash 复制代码
vim /etc/my.cnf

在 [mysqld] 段下只加一行:

ini 复制代码
[mysqld]
server-id=101        # 唯一标识,从库用 101,与主库 100 不重复

为什么从库不需要 log-bin

对比主库与从库的配置:

配置项 主库 从库 原因
log-bin 必须开 可不开 主库的 binlog 是数据源头,从库要来拉
server-id 必须设 必须设 标识实例身份,防止循环复制

从库只要不是"别人的主库"(级联复制场景),就不需要开 binlog。它接收主库的日志写到 relay log 即可。

级联复制例外 :如果拓扑是 Master → Slave-A → Slave-B(Slave-B 从 Slave-A 同步),那 Slave-A 既要开 log-bin(供 Slave-B 拉取),也要配置 log-slave-updates(把自己回放的操作也记进自己的 binlog)。

二、第二步:重启服务

bash 复制代码
systemctl restart mysqld
systemctl status mysqld

三、第三步:CHANGE MASTER TO

登录从库的 MySQL,执行:

sql 复制代码
CHANGE MASTER TO
    MASTER_HOST='192.168.138.100',
    MASTER_USER='xiaoming',
    MASTER_PASSWORD='Root@123456',
    MASTER_LOG_FILE='mysql-bin.000005',
    MASTER_LOG_POS=441;

五个参数逐项说明

参数 值来源 说明
MASTER_HOST 主库 IP 192.168.138.100
MASTER_USER 主库创建的复制账号 xiaoming
MASTER_PASSWORD 该账号的密码 Root@123456
MASTER_LOG_FILE 主库 SHOW MASTER STATUS 的 File 列 mysql-bin.000005
MASTER_LOG_POS 主库 SHOW MASTER STATUS 的 Position 列 441

后两个参数必须按主库实际查出来的值填,不能照抄教程。每次重新配置都要重新查一次。

完整参数列表(更严谨的写法):

sql 复制代码
CHANGE MASTER TO
    MASTER_HOST='192.168.138.100',
    MASTER_PORT=3306,
    MASTER_USER='xiaoming',
    MASTER_PASSWORD='Root@123456',
    MASTER_LOG_FILE='mysql-bin.000005',
    MASTER_LOG_POS=441,
    MASTER_CONNECT_RETRY=10;   -- 断线重连间隔(秒)

真实报错:IO Thread 正在运行

课程里执行 CHANGE MASTER TO 时报了错:

err 复制代码
ERROR 3021 (HY000): This operation cannot be performed with a running slave io thread;
run STOP SLAVE FOR CHANNEL '' first.

原因 :这台从库之前配置过 slave,IO Thread 已经自动跑起来了。要修改主库配置,必须先停掉复制线程。

解决:

sql 复制代码
STOP SLAVE;
-- 然后重新执行 CHANGE MASTER TO ...

#mermaid-svg-a2I8Uc4zMdeKR3jv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a2I8Uc4zMdeKR3jv .error-icon{fill:#552222;}#mermaid-svg-a2I8Uc4zMdeKR3jv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a2I8Uc4zMdeKR3jv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .marker.cross{stroke:#333333;}#mermaid-svg-a2I8Uc4zMdeKR3jv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a2I8Uc4zMdeKR3jv p{margin:0;}#mermaid-svg-a2I8Uc4zMdeKR3jv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .cluster-label text{fill:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .cluster-label span{color:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .cluster-label span p{background-color:transparent;}#mermaid-svg-a2I8Uc4zMdeKR3jv .label text,#mermaid-svg-a2I8Uc4zMdeKR3jv span{fill:#333;color:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .node rect,#mermaid-svg-a2I8Uc4zMdeKR3jv .node circle,#mermaid-svg-a2I8Uc4zMdeKR3jv .node ellipse,#mermaid-svg-a2I8Uc4zMdeKR3jv .node polygon,#mermaid-svg-a2I8Uc4zMdeKR3jv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .rough-node .label text,#mermaid-svg-a2I8Uc4zMdeKR3jv .node .label text,#mermaid-svg-a2I8Uc4zMdeKR3jv .image-shape .label,#mermaid-svg-a2I8Uc4zMdeKR3jv .icon-shape .label{text-anchor:middle;}#mermaid-svg-a2I8Uc4zMdeKR3jv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .rough-node .label,#mermaid-svg-a2I8Uc4zMdeKR3jv .node .label,#mermaid-svg-a2I8Uc4zMdeKR3jv .image-shape .label,#mermaid-svg-a2I8Uc4zMdeKR3jv .icon-shape .label{text-align:center;}#mermaid-svg-a2I8Uc4zMdeKR3jv .node.clickable{cursor:pointer;}#mermaid-svg-a2I8Uc4zMdeKR3jv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .arrowheadPath{fill:#333333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a2I8Uc4zMdeKR3jv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a2I8Uc4zMdeKR3jv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a2I8Uc4zMdeKR3jv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a2I8Uc4zMdeKR3jv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .cluster text{fill:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv .cluster span{color:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a2I8Uc4zMdeKR3jv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a2I8Uc4zMdeKR3jv rect.text{fill:none;stroke-width:0;}#mermaid-svg-a2I8Uc4zMdeKR3jv .icon-shape,#mermaid-svg-a2I8Uc4zMdeKR3jv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a2I8Uc4zMdeKR3jv .icon-shape p,#mermaid-svg-a2I8Uc4zMdeKR3jv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a2I8Uc4zMdeKR3jv .icon-shape .label rect,#mermaid-svg-a2I8Uc4zMdeKR3jv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a2I8Uc4zMdeKR3jv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a2I8Uc4zMdeKR3jv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a2I8Uc4zMdeKR3jv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是(曾配置过)
否(全新从库)
执行 CHANGE MASTER TO
IO Thread 是否在运行
ERROR 3021
STOP SLAVE
重新执行 CHANGE MASTER TO

执行成功

全新安装的从库不会遇到这个错误。课程里遇到是因为环境此前已经配过一次。

四、第四步:START SLAVE

sql 复制代码
START SLAVE;

这条命令启动了从库的两个复制线程:

  • IO Thread:连接主库,拉取 binlog 写入 relay log
  • SQL Thread:解析 relay log 并回放

对应的其他命令:

命令 作用
START SLAVE 启动全部复制线程
STOP SLAVE 停止全部复制线程
START SLAVE IO_THREAD 只启动 IO Thread
START SLAVE SQL_THREAD 只启动 SQL Thread
RESET SLAVE 清空复制配置(会删除 master.info 与 relay-log.info)
RESET SLAVE ALL 彻底清空,包括 CHANGE MASTER TO 的信息

RESET SLAVE 与 RESET SLAVE ALL 的区别 :前者清空位点信息但保留 CHANGE MASTER TO 的连接配置;后者连连接配置一起清。想彻底重配用 ALL。MySQL 8.0.22+ 中 RESET SLAVE 已被 RESET REPLICA 取代。

五、第五步:查看从库状态

sql 复制代码
SHOW SLAVE STATUS\G

输出很长(约 50 个字段)。课程里给了一个实用技巧:把结果复制到文本编辑器里看,比在终端里横向滚动舒服得多。

必须关注的字段

sql 复制代码
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.138.100
                  Master_User: xiaoming
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000005
          Read_Master_Log_Pos: 441
               Relay_Log_File: mysqld-relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000005
             Slave_IO_Running: Yes          ← 关键
            Slave_SQL_Running: Yes          ← 关键
          Seconds_Behind_Master: 0
               Last_IO_Errno: 0
               Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
字段 正常值 说明
Slave_IO_Running Yes IO Thread 运行中
Slave_SQL_Running Yes SQL Thread 运行中
Slave_IO_State Waiting for master to send event 正在等待主库推送事件
Master_Host 192.168.138.100 确认连接的是正确的主库
Master_User xiaoming 确认用的是复制账号
Seconds_Behind_Master 0 无延迟
Last_IO_Error / Last_SQL_Error 空 无错误

判断标准:两个 Running 都是 Yes 就说明主从复制正常。
#mermaid-svg-wddNrAytchyyNVxu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wddNrAytchyyNVxu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wddNrAytchyyNVxu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wddNrAytchyyNVxu .error-icon{fill:#552222;}#mermaid-svg-wddNrAytchyyNVxu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wddNrAytchyyNVxu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wddNrAytchyyNVxu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wddNrAytchyyNVxu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wddNrAytchyyNVxu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wddNrAytchyyNVxu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wddNrAytchyyNVxu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wddNrAytchyyNVxu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wddNrAytchyyNVxu .marker.cross{stroke:#333333;}#mermaid-svg-wddNrAytchyyNVxu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wddNrAytchyyNVxu p{margin:0;}#mermaid-svg-wddNrAytchyyNVxu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wddNrAytchyyNVxu .cluster-label text{fill:#333;}#mermaid-svg-wddNrAytchyyNVxu .cluster-label span{color:#333;}#mermaid-svg-wddNrAytchyyNVxu .cluster-label span p{background-color:transparent;}#mermaid-svg-wddNrAytchyyNVxu .label text,#mermaid-svg-wddNrAytchyyNVxu span{fill:#333;color:#333;}#mermaid-svg-wddNrAytchyyNVxu .node rect,#mermaid-svg-wddNrAytchyyNVxu .node circle,#mermaid-svg-wddNrAytchyyNVxu .node ellipse,#mermaid-svg-wddNrAytchyyNVxu .node polygon,#mermaid-svg-wddNrAytchyyNVxu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wddNrAytchyyNVxu .rough-node .label text,#mermaid-svg-wddNrAytchyyNVxu .node .label text,#mermaid-svg-wddNrAytchyyNVxu .image-shape .label,#mermaid-svg-wddNrAytchyyNVxu .icon-shape .label{text-anchor:middle;}#mermaid-svg-wddNrAytchyyNVxu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wddNrAytchyyNVxu .rough-node .label,#mermaid-svg-wddNrAytchyyNVxu .node .label,#mermaid-svg-wddNrAytchyyNVxu .image-shape .label,#mermaid-svg-wddNrAytchyyNVxu .icon-shape .label{text-align:center;}#mermaid-svg-wddNrAytchyyNVxu .node.clickable{cursor:pointer;}#mermaid-svg-wddNrAytchyyNVxu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wddNrAytchyyNVxu .arrowheadPath{fill:#333333;}#mermaid-svg-wddNrAytchyyNVxu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wddNrAytchyyNVxu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wddNrAytchyyNVxu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wddNrAytchyyNVxu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wddNrAytchyyNVxu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wddNrAytchyyNVxu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wddNrAytchyyNVxu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wddNrAytchyyNVxu .cluster text{fill:#333;}#mermaid-svg-wddNrAytchyyNVxu .cluster span{color:#333;}#mermaid-svg-wddNrAytchyyNVxu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-wddNrAytchyyNVxu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wddNrAytchyyNVxu rect.text{fill:none;stroke-width:0;}#mermaid-svg-wddNrAytchyyNVxu .icon-shape,#mermaid-svg-wddNrAytchyyNVxu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wddNrAytchyyNVxu .icon-shape p,#mermaid-svg-wddNrAytchyyNVxu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wddNrAytchyyNVxu .icon-shape .label rect,#mermaid-svg-wddNrAytchyyNVxu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wddNrAytchyyNVxu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wddNrAytchyyNVxu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wddNrAytchyyNVxu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Yes
No
Yes
No
SHOW SLAVE STATUS
Slave_IO_Running
Slave_SQL_Running
查看 Last_IO_Error

常见:网络不通 / 账号密码错 / 位点错
✓ 复制正常
查看 Last_SQL_Error

常见:回放 SQL 失败 / 表结构不一致 / 主键冲突

六、功能测试

用最直观的方式验证:在主库操作,看从库是否跟着变。

测试一:建库

sql 复制代码
-- 在主库执行
CREATE DATABASE itcast DEFAULT CHARACTER SET utf8mb4;

从库刷新,同样出现了 itcast 数据库。✅

测试二:建表

sql 复制代码
-- 在主库的 itcast 库中执行
USE itcast;

CREATE TABLE user (
    id   INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50)
);

从库刷新,itcast 库下出现了 user 表。✅

测试三:插入数据

sql 复制代码
-- 主库
INSERT INTO user (id, name) VALUES (1, 'ABC');

从库打开 user 表,看到了 (1, 'ABC')。✅

测试四:修改数据

sql 复制代码
-- 主库
UPDATE user SET name = 'test' WHERE id = 1;

从库刷新,值变成 test。✅

测试五:删除数据

sql 复制代码
-- 主库
DELETE FROM user WHERE id = 1;

从库刷新,记录消失。✅
从库 192.168.138.101 binlog 主库 192.168.138.100 从库 192.168.138.101 binlog 主库 192.168.138.100 #mermaid-svg-UiFKoZEIgrhPn7ec{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UiFKoZEIgrhPn7ec .error-icon{fill:#552222;}#mermaid-svg-UiFKoZEIgrhPn7ec .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UiFKoZEIgrhPn7ec .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UiFKoZEIgrhPn7ec .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UiFKoZEIgrhPn7ec .marker.cross{stroke:#333333;}#mermaid-svg-UiFKoZEIgrhPn7ec svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UiFKoZEIgrhPn7ec p{margin:0;}#mermaid-svg-UiFKoZEIgrhPn7ec .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UiFKoZEIgrhPn7ec text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UiFKoZEIgrhPn7ec .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-UiFKoZEIgrhPn7ec .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-UiFKoZEIgrhPn7ec #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-UiFKoZEIgrhPn7ec .sequenceNumber{fill:white;}#mermaid-svg-UiFKoZEIgrhPn7ec #sequencenumber{fill:#333;}#mermaid-svg-UiFKoZEIgrhPn7ec #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-UiFKoZEIgrhPn7ec .messageText{fill:#333;stroke:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UiFKoZEIgrhPn7ec .labelText,#mermaid-svg-UiFKoZEIgrhPn7ec .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .loopText,#mermaid-svg-UiFKoZEIgrhPn7ec .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-UiFKoZEIgrhPn7ec .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UiFKoZEIgrhPn7ec .noteText,#mermaid-svg-UiFKoZEIgrhPn7ec .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-UiFKoZEIgrhPn7ec .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UiFKoZEIgrhPn7ec .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UiFKoZEIgrhPn7ec .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-UiFKoZEIgrhPn7ec .actorPopupMenu{position:absolute;}#mermaid-svg-UiFKoZEIgrhPn7ec .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-UiFKoZEIgrhPn7ec .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-UiFKoZEIgrhPn7ec .actor-man circle,#mermaid-svg-UiFKoZEIgrhPn7ec line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-UiFKoZEIgrhPn7ec :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} CREATE DATABASE itcast itcast 库出现 ✓ CREATE TABLE user user 表出现 ✓ INSERT / UPDATE / DELETE 数据同步 ✓ 记录 DDLIO Thread 拉取SQL Thread 回放记录 DDL拉取回放记录 DML拉取回放

DDL 也会同步 。这点值得注意------CREATE DATABASE、CREATE TABLE、ALTER TABLE 都会写入 binlog 并同步到从库。所以不要在从库上手工建表,会造成冲突。

反向验证:从库写入不会同步到主库

主从复制是单向 的。在从库上 INSERT 一条数据,主库不会有任何变化------而且这会造成主从数据不一致,是非常危险的操作。

从库必须设为只读。推荐配置:

sql 复制代码
SET GLOBAL read_only = 1;      -- 普通用户只读(root 仍可写)
SET GLOBAL super_read_only = 1; -- 连 root 也只读(MySQL 5.7+)

注意 read_only 对拥有 SUPER 权限的用户无效,所以生产环境建议同时开 super_read_only。但要注意:复制线程(SQL Thread)不受 read_only 限制,它仍能正常回放数据。

七、常见故障与排查

现象 可能原因 排查与解决
Slave_IO_Running: Connecting 连不上主库 检查 IP、端口、防火墙、账号主机限制
Last_IO_Error: error connecting to master ... Access denied 账号密码错,或主机不允许 主库 SELECT user,host FROM mysql.user;用 mysql -h 主库 -u xiaoming -p 实测
Last_IO_Error: Could not find first log file MASTER_LOG_FILE 填错 主库重查 SHOW MASTER STATUS,重新 CHANGE MASTER TO
Slave_SQL_Running: No + Last_SQL_Error: Error 'Duplicate entry' 从库已有相同数据,回放主键冲突 见下方
Seconds_Behind_Master 持续增长 从库回放速度跟不上 开启并行复制;检查从库 IO、锁等待
从库数据比主库少 搭建前从库已有数据未清理 应先用 mysqldump 全量导入基线数据,再配置复制

主键冲突的处理

如果在配置复制前,从库上已经有数据,回放时可能报 Duplicate entry。两种处理方式:

sql 复制代码
-- 方式一:跳过指定数量的错误(临时救急,可能造成数据不一致)
STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;
sql 复制代码
-- 方式二(推荐):重新搭建复制
-- 1. 主库全量备份
mysqldump -uroot -p --all-databases --master-data=2 --single-transaction > /tmp/full.sql

-- 2. 从库导入
mysql -uroot -p < /tmp/full.sql

-- 3. 从 full.sql 的 CHANGE MASTER TO 注释中读取位点,重新配置
--    -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000005', MASTER_LOG_POS=441;

-- 4. 重新配置并启动
CHANGE MASTER TO ...;
START SLAVE;

--master-data=2 会在导出文件的开头以注释形式 写入 CHANGE MASTER TO MASTER_LOG_FILE=..., MASTER_LOG_POS=...,正好告诉你要从哪个位点开始复制。这是搭建主从最标准的做法。

八、完整操作脚本

bash 复制代码
#!/bin/bash
# ============================================
# MySQL 从库 Slave 配置脚本
# 使用前请先在主库执行 SHOW MASTER STATUS 获取 FILE 与 POSITION
# ============================================

set -e

MYSQL_CNF="/etc/my.cnf"
SERVER_ID=101
MYSQL_ROOT_PASSWORD="root"

MASTER_HOST="192.168.138.100"
MASTER_USER="xiaoming"
MASTER_PASSWORD="Root@123456"

# 这两个值必须替换成主库 SHOW MASTER STATUS 的实际输出
MASTER_LOG_FILE="mysql-bin.000005"
MASTER_LOG_POS=441

echo "========== 第一步:修改配置文件 =========="
cp ${MYSQL_CNF} ${MYSQL_CNF}.bak.$(date +%Y%m%d%H%M%S)

if ! grep -q "^server-id" ${MYSQL_CNF}; then
    sed -i "/^\[mysqld\]/a server-id=${SERVER_ID}" ${MYSQL_CNF}
    echo "已追加 server-id=${SERVER_ID}"
else
    echo "server-id 已存在,跳过"
fi

echo "========== 第二步:重启 MySQL =========="
systemctl restart mysqld
sleep 3

echo "========== 第三步:配置主库连接信息 =========="
mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "STOP SLAVE;" 2>/dev/null || echo "复制线程未运行,无需停止"

mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "
STOP SLAVE;
CHANGE MASTER TO
    MASTER_HOST='${MASTER_HOST}',
    MASTER_PORT=3306,
    MASTER_USER='${MASTER_USER}',
    MASTER_PASSWORD='${MASTER_PASSWORD}',
    MASTER_LOG_FILE='${MASTER_LOG_FILE}',
    MASTER_LOG_POS=${MASTER_LOG_POS},
    MASTER_CONNECT_RETRY=10;
START SLAVE;
"

echo "========== 第四步:查看从库状态 =========="
sleep 2
mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "
SHOW SLAVE STATUS\G
" | grep -E "Slave_IO_State|Master_Host|Master_User|Slave_IO_Running|Slave_SQL_Running|Seconds_Behind_Master|Last_IO_Error|Last_SQL_Error"

echo ""
echo "确认 Slave_IO_Running 与 Slave_SQL_Running 均为 Yes 即配置成功。"

一键测试脚本

bash 复制代码
#!/bin/bash
# 主从复制验证脚本

MASTER="192.168.138.100"
SLAVE="192.168.138.101"
PWD="root"

echo "===== 在主库创建测试数据 ====="
mysql -h ${MASTER} -uroot -p${PWD} -e "
DROP DATABASE IF EXISTS repl_test;
CREATE DATABASE repl_test;
USE repl_test;
CREATE TABLE t_demo (id INT PRIMARY KEY, name VARCHAR(20));
INSERT INTO t_demo VALUES (1, 'hello');
"

sleep 2

echo "===== 从库查询(应能看到) ====="
mysql -h ${SLAVE} -uroot -p${PWD} -e "SELECT * FROM repl_test.t_demo;"

echo "===== 主库更新 ====="
mysql -h ${MASTER} -uroot -p${PWD} -e "UPDATE repl_test.t_demo SET name='world' WHERE id=1;"

sleep 2

echo "===== 从库查询(应已更新) ====="
mysql -h ${SLAVE} -uroot -p${PWD} -e "SELECT * FROM repl_test.t_demo;"

echo "===== 清理 ====="
mysql -h ${MASTER} -uroot -p${PWD} -e "DROP DATABASE repl_test;"

九、项目环境衔接

主从复制搭建完成后,项目具备了读写分离的数据基础:

节点 IP 角色 应用用途
Master 192.168.138.100 主库 承接所有写操作
Slave 192.168.138.101 从库 承接所有读操作

接下来的改造是在应用层 实现读写分离------让 Sharding-JDBC 根据 SQL 类型自动路由:

yaml 复制代码
spring:
  shardingsphere:
    datasource:
      names: master,slave
      master:
        url: jdbc:mysql://192.168.138.100:3306/reggie?characterEncoding=utf-8
        username: root
        password: root
      slave:
        url: jdbc:mysql://192.168.138.101:3306/reggie?characterEncoding=utf-8
        username: root
        password: root
    masterslave:
      load-balance-algorithm-type: round_robin    # 从库负载均衡策略:轮询
      name: dataSource
      master-data-source-name: master             # 主库数据源名称
      slave-data-source-names: slave              # 从库数据源名称列表

从库的应用账号 :Sharding-JDBC 里配置的是应用账号(这里是 root),不是复制账号 xiaoming。从库需要给应用账号授予 SELECT 权限:

sql 复制代码
-- 在从库执行
GRANT SELECT ON reggie.* TO 'root'@'%';
FLUSH PRIVILEGES;

更安全的做法是为读操作单独创建只读账号:

sql 复制代码
CREATE USER 'reggie_read'@'%' IDENTIFIED BY 'Read@123456';
GRANT SELECT ON reggie.* TO 'reggie_read'@'%';
FLUSH PRIVILEGES;

这样即使应用被注入,攻击者也无法通过读库写入数据。

API 速览

命令 / 配置项 作用
CHANGE MASTER TO ... 配置从库要连接的主库信息
MASTER_HOST / MASTER_PORT 主库地址与端口
MASTER_USER / MASTER_PASSWORD 复制账号凭证
MASTER_LOG_FILE / MASTER_LOG_POS 起始 binlog 文件名与位置
MASTER_CONNECT_RETRY 断线重连间隔(秒)
START SLAVE 启动复制线程
STOP SLAVE 停止复制线程
RESET SLAVE / RESET SLAVE ALL 清空复制配置
SHOW SLAVE STATUS 查看从库复制状态
Slave_IO_Running IO Thread 状态,应为 Yes
Slave_SQL_Running SQL Thread 状态,应为 Yes
Seconds_Behind_Master 主从延迟秒数
Last_IO_Error / Last_SQL_Error 错误信息
SET GLOBAL read_only = 1 设置只读(对 SUPER 用户无效)
SET GLOBAL super_read_only = 1 设置只读(含 SUPER 用户,5.7+)
SET GLOBAL sql_slave_skip_counter = N 跳过 N 个复制错误
mysqldump --master-data=2 全量备份并记录 binlog 位点
SHOW PROCESSLIST 查看当前连接与线程

官方文档

主题 链接
配置复制从库 https://dev.mysql.com/doc/refman/8.0/en/replication-howto-slavebaseconfig.html
CHANGE MASTER TO 语法 https://dev.mysql.com/doc/refman/8.0/en/change-master-to.html
START SLAVE / STOP SLAVE https://dev.mysql.com/doc/refman/8.0/en/replication-threads-commands.html
SHOW SLAVE STATUS https://dev.mysql.com/doc/refman/8.0/en/show-slave-status.html
复制故障排查 https://dev.mysql.com/doc/refman/8.0/en/replication-problems.html
mysqldump 备份 https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html

总结

从库配置四步:改 server-id=101 → 重启 → CHANGE MASTER TO → START SLAVE。判断成功的标准是 SHOW SLAVE STATUS 里 Slave_IO_Running 与 Slave_SQL_Running 双 Yes。

几个实操要点:

从库不需要开 log-bin 。只有"要作为别人的主库"时(级联复制)才需要,那时还要加 log-slave-updates。

CHANGE MASTER TO 的 MASTER_LOG_FILE 与 MASTER_LOG_POS 必须来自主库 SHOW MASTER STATUS 的实时输出,不能照抄教程。这两个值就是"从哪个位置开始复制"的坐标。

遇到 ERROR 3021 先 STOP SLAVE 。这是因为从库此前配过复制、IO Thread 已在运行,修改配置前必须先停。全新从库不会遇到。

主从复制是单向的 。从库写入不会同步到主库,反而会造成数据不一致。生产必须设 read_only(建议加 super_read_only,因为普通 read_only 对 SUPER 用户无效)。

DDL 也会同步------建库、建表、改表都会传到从库,所以不要在从库上手工建表。

功能验证用最朴素的方式:主库建库 → 建表 → 插入 → 修改 → 删除,从库逐一确认跟着变化。五步全过就说明复制链路完全打通。

另外补充了课程没讲的但生产必需的两点:一是从库要设只读 防止误写,二是从库要给应用账号授权 (建议单独建只读账号 reggie_read,而不是直接用 root)。

数据基础就绪,下一篇进入应用层:Sharding-JDBC 入门案例,用代码实现读写分离。

相关推荐
子一!!1 小时前
集成Spring家族的Spring论坛实战==一阶段
java·后端·spring
何中应1 小时前
Maven 执行控制台中文乱码问题
java·maven·intellij-idea
Zhou1411361 小时前
SpringSecurity_01_入门与认证
java
java_nnnn1 小时前
JavaEE进阶-CSS初识
java·前端·css·java-ee·html
知识分享小能手1 小时前
C++ 学习教程,从入门到精通,C++内存模型和名称空间 — 详细知识点总结(9)
开发语言·c++·学习
Wang's Blog1 小时前
Java 项目实战: 外卖平台优化-使用Redis作为缓存产品与TTL配置
java·redis·缓存
2601_962218611 小时前
c++中多重继承与虚继承的实现
开发语言·c++
谢亮_vipxieliang1 小时前
ValidX 在微服务架构中的验证策略
java·spring boot·spring·spring cloud·hibernate
其实防守也摸鱼1 小时前
内网安全防护实践:从攻击视角理解防御要点
java·前端·安全·架构·自动化·nps