读写分离: 从库 Slave 配置与主从复制验证
纲要
本篇对应课程 day02 的「配置从库 Slave」与「主从复制测试」环节,完成复制链路的最后一环:
- 修改从库配置 :只需设置
server-id=101(从库不产生binlog供他人复制,无需开log-bin) CHANGE MASTER TO:指定主库IP、复制账号、binlog文件名与位点START SLAVE:启动复制线程;遇到IO Thread已运行时先STOP SLAVESHOW 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 logSQL 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 一条数据,主库不会有任何变化------而且这会造成主从数据不一致,是非常危险的操作。
从库必须设为只读。推荐配置:
sqlSET 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;更安全的做法是为读操作单独创建只读账号:
sqlCREATE 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 入门案例,用代码实现读写分离。