尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离

MySQL 主从复制讲透:binlog 原理、搭建步骤与读写分离 个人主页 for_ever_love__ 欢迎各位大佬莅临其他栏目: 大模型开发从0到1 其他栏目: iOS项目总结大全 其他栏目: 我想学python了 其他栏目: iOS UI 文章目录MySQL 主从复制讲透binlog 原理、搭建步骤与读写分离一、复制解决什么问题二、复制的三个线程三、binlog复制的数据源3.1 binlog 是什么3.2 三种格式重点3.3 ROW 格式的日志太大怎么办四、复制的三种模式五、搭建步骤一步步来5.1 主库配置5.2 从库配置5.3 数据初始化5.4 启动复制5.5 检查状态六、主从延迟最头疼的问题6.1 延迟从哪来6.2 最核心的原因单线程回放6.3 大事务延迟的另一大来源6.4 延迟怎么监控七、读写分离怎么落地7.1 应用层路由最常见7.2 中间件方案7.3 用 Hint 强制走主库八、常见故障处理8.1 主键冲突导致复制中断8.2 从库落后太多8.3 主从不一致校验九、小结MySQL 主从复制讲透binlog 原理、搭建步骤与读写分离单库扛不住读压力时第一个想到的方案就是主从复制。本篇讲清楚复制是怎么工作的三个线程 两类日志、binlog 的三种格式有什么区别、怎么搭建、以及最让人头疼的主从延迟怎么排查和缓解。一、复制解决什么问题问题主从怎么解决读压力大写走主库读分散到多个从库单点故障主库挂了可以切从库备份影响业务在从库上做备份不影响主库分析查询拖垮线上慢查询、报表跑在从库⚠️ 要明确主从复制不解决写压力。所有写还是在主库从库只是分担读。写压力大要靠分库分表下一篇。二、复制的三个线程主库 (Master) 从库 (Slave) ┌──────────────┐ ┌──────────────────┐ │ binlog │ │ relay log │ │ (二进制日志) │ │ (中继日志) │ └──────┬───────┘ └────────┬─────────┘ │ │ │ ① 读 binlog ③ 回放 ┌───▼─────────┐ ┌──────▼──────┐ │ dump 线程 │ ──── 传输 ────▶ │ SQL 线程 │ │ (Binlog │ ② │ (回放 relay │ │ Dump) │ ◀──── 请求 ────── │ log) │ └─────────────┘ └─────────────┘ ┌──────────────┐ │ IO 线程 │ │ (接收并写 │ │ relay log) │ └──────────────┘完整流程主库事务提交时把变更记录写进binlog从库 IO 线程连接主库请求 binlog主库起一个dump 线程把 binlog 推过来IO 线程收到后写进本地的relay log中继日志从库 SQL 线程读 relay log把变更回放replay到从库三个线程记住主库Binlog Dump 线程从库IO 线程拉日志SQL 线程回放三、binlog复制的数据源3.1 binlog 是什么binlog 是服务层不是 InnoDB 独有的二进制日志记录所有数据变更DDL 和 DML不记录 SELECT。三个作用复制、数据恢复、审计。-- 查看 binlog 是否开启SHOWVARIABLESLIKElog_bin;-- 查看当前 binlog 文件列表SHOWBINARYLOGS;-- 查看正在写的 binlogSHOWMASTERSTATUS;-- 查看 binlog 内容SHOWBINLOG EVENTSINmysql-bin.000001LIMIT10;3.2 三种格式重点SHOWVARIABLESLIKEbinlog_format;格式记录内容优点缺点STATEMENT记录SQL 语句原文日志小某些语句不安全如NOW()、UUID()、触发器ROW记录每行数据的变化绝对安全日志大改 10 万行就记 10 万条MIXED混合安全用 STATEMENT不安全用 ROW折中行为不完全可预测生产必须用 ROW 格式这是共识。原因-- STATEMENT 格式的经典问题UPDATEordersSETupdated_atNOW()WHEREstatuspaid;-- 主库执行时 NOW() 是 10:00-- 从库回放时 NOW() 是 10:05 ← 数据不一致ROW 格式记录的是哪一行从什么值改成什么值跟执行时间无关绝对安全。SETGLOBALbinlog_formatROW;-- my.cnf: binlog_format ROW⚠️READ COMMITTED 隔离级别下 binlog 必须用 ROW因为 STATEMENT 在 RC 下会有主从不一致问题。3.3 ROW 格式的日志太大怎么办MySQL 5.6 有个参数控制 ROW 格式记录多少内容SHOWVARIABLESLIKEbinlog_row_image;-- FULL : 记录所有列默认最安全-- MINIMAL : 只记录变更的列 定位行所需的列日志最小-- NOBLOB : 不记录没变更的 BLOB/TEXTMINIMAL能显著减小日志但有约束表必须有主键否则无法唯一定位行。四、复制的三种模式模式说明特点异步复制默认主库写完 binlog 就返回不等从库性能好可能丢数据半同步复制主库等至少一个从库收到才返回折中减少丢失风险组复制Group Replication基于 Paxos强一致复杂MySQL InnoDB Cluster 用生产常用半同步-- 主库和从库都装插件INSTALL PLUGIN rpl_semi_sync_masterSONAMEsemisync_master.so;INSTALL PLUGIN rpl_semi_sync_slaveSONAMEsemisync_slave.so;-- 开启SETGLOBALrpl_semi_sync_master_enabled1;SETGLOBALrpl_semi_sync_slave_enabled1;-- 超时时间超过就退化成异步SETGLOBALrpl_semi_sync_master_timeout1000;-- 毫秒五、搭建步骤一步步来5.1 主库配置# my.cnf [mysqld] server-id 1 # 必须唯一 log_bin /var/log/mysql/mysql-bin binlog_format ROW binlog_row_image FULL expire_logs_days 7 # 自动清理 7 天前的 binlog-- 创建复制专用账号CREATEUSERrepl%IDENTIFIEDBYStrongPass123;GRANTREPLICATIONSLAVEON*.*TOrepl%;FLUSHPRIVILEGES;-- 查看主库状态记下 File 和 PositionSHOWMASTERSTATUS;-- ------------------------------ | mysql-bin.000003 | 154 |-- ----------------------------5.2 从库配置[mysqld] server-id 2 # 必须跟主库不同 relay_log /var/log/mysql/relay-bin read_only 1 # 从库只读对 SUPER 权限用户无效 super_read_only 1 # 8.0连 SUPER 也只读5.3 数据初始化主库已有数据时需要先同步一份全量到从库# 用 mysqldump 导出--single-transaction 保证一致性快照不锁表mysqldump --single-transaction --master-data2\--all-databases-uroot-pfull.sql# 在从库导入mysql-uroot-pfull.sql--master-data2会把CHANGE MASTER TO需要的binlog 文件名和位置以注释形式写进 SQL 文件导入后直接能看到。5.4 启动复制-- MySQL 8.0.23 推荐新语法CHANGEREPLICATIONSOURCETOSOURCE_HOST10.0.0.1,SOURCE_USERrepl,SOURCE_PASSWORDStrongPass123,SOURCE_LOG_FILEmysql-bin.000003,SOURCE_LOG_POS154;STARTREPLICA;-- 老语法5.7/8.0 早期CHANGE MASTERTOMASTER_HOST10.0.0.1,...;STARTSLAVE;5.5 检查状态SHOWREPLICASTATUS\G-- 8.0.22-- 或 SHOW SLAVE STATUS\G-- 关键看这两行5.7/8.0 单线程Slave_IO_Running: Yes Slave_SQL_Running: Yes-- 8.0 多线程复制下看Replica_IO_Running: Yes Replica_SQL_Running: Yes Seconds_Behind_Master:0-- 延迟秒数Last_IO_Error:-- IO 线程错误Last_SQL_Error:-- SQL 线程错误两个 Yes 才算正常。任何一个 No 就去看对应的Last_*_Error。六、主从延迟最头疼的问题6.1 延迟从哪来主库执行 → 写 binlog → 网络传输 → 从库写 relay log → SQL 线程回放延迟可能来自任何一环环节常见原因主库写 binlog 慢大事务、磁盘 IO 差网络传输慢跨机房、带宽不足从库回放慢最常见单线程回放跟不上主库并发写入从库负载高从库上跑了大量读查询抢资源6.2 最核心的原因单线程回放主库可以几百个连接并发写但从库 SQL 线程只有一个必须串行回放。主库 1 秒写 1000 个事务从库 1 秒只能回放 200 个延迟就会持续累积。解决方案多线程复制MTS-- MySQL 5.7 支持基于逻辑时钟的并行回放SETGLOBALslave_parallel_typeLOGICAL_CLOCK;SETGLOBALslave_parallel_workers8;-- 8 个 worker 线程-- 8.0 默认已经是 LOGICAL_CLOCKworkers 默认 4SHOWVARIABLESLIKEslave_parallel%;LOGICAL_CLOCK的原理同一组提交的事务在主库上没有冲突可以并行回放。所以主库并发度越高从库也越能并行。⚠️ 但有个前提主库的binlog_group_commit相关参数要调好否则事务组划分得太细并行度上不去。SETGLOBALbinlog_group_commit_sync_delay100;-- 微秒稍微攒一攒SETGLOBALbinlog_group_commit_sync_no_delay_count10;6.3 大事务延迟的另一大来源-- ❌ 一个事务改 100 万行UPDATEordersSETstatusarchivedWHEREcreated_at2025-01-01;-- 从库要回放这个巨大的事务期间延迟飙升解决拆成小批量# 分批更新每批 1000 行whileTrue:ncursor.execute( UPDATE orders SET statusarchived WHERE created_at 2025-01-01 AND status ! archived LIMIT 1000 )conn.commit()ifn0:breaktime.sleep(0.1)# 给从库一点喘息时间纪律禁止在主库执行影响行数超过 1 万的单条 DML。6.4 延迟怎么监控SHOWREPLICASTATUS\G-- Seconds_Behind_Master⚠️Seconds_Behind_Master不完全可靠它是用时间戳差值算的主库没写入时会显示 0但实际可能有积压因为没新的 binlog 事件来更新时间主从机器时钟不同步时数值不准大事务期间它不准更可靠的判断对比 binlog 位点-- 主库SHOWMASTERSTATUS;-- Position: 1540000-- 从库SHOWREPLICASTATUS\G-- 看 Relay_Source_Log_File / Exec_Source_Log_Pos-- 对比主库的 File/Position 差多少或者用pt-heartbeatPercona 工具在主库写心跳表从库读算出真实延迟。这是生产最准的做法。七、读写分离怎么落地7.1 应用层路由最常见classRouter:def__init__(self,master,slaves):self.master,self.slavesmaster,slavesdefget_conn(self,sql:str,in_transaction:bool):# 写操作、事务内、或刚写完需要读自己写入的数据 → 走主库ifin_transactionoris_write(sql):returnself.masterreturnrandom.choice(self.slaves)# 读走从库⚠️关键坑写完立刻读会因为延迟读不到自己刚写的数据。# ❌ 写完马上读从库可能读到旧数据db_master.execute(INSERT INTO orders ...)rowdb_slave.query(SELECT * FROM orders WHERE id %s,(new_id,))# 可能查不到# ✅ 方案一这类读强制走主库rowdb_master.query(SELECT ...)# ✅ 方案二写完后的 N 秒内读主库基于 GTID 或时间戳判断这个坑叫**读己之写一致性问题**是读写分离落地的第一大坑。7.2 中间件方案不想改代码可以用中间件它解析 SQL 自动路由中间件特点ProxySQL功能强支持查询规则、连接池、故障转移MyCat国产支持分库分表ShardingSphereApache 项目生态好代价是多一跳网络、多一个组件要维护。7.3 用 Hint 强制走主库-- 在 SQL 里加注释中间件识别SELECT/* MASTER */*FROMordersWHEREid1;八、常见故障处理8.1 主键冲突导致复制中断SHOWREPLICASTATUS\G-- Last_SQL_Error: Duplicate entry 5 for key PRIMARY原因从库被写入了数据或者主从数据本来就不一致。快速恢复慎用会丢数据STOP REPLICA;SETGLOBALsql_slave_skip_counter1;-- 跳过 1 个事件STARTREPLICA;更好的做法用pt-table-sync修复不一致或者重建从库。8.2 从库落后太多如果延迟已经几十分钟且追不上重建从库反而更快重新 mysqldump 一份全量重新 START REPLICA。8.3 主从不一致校验# Percona 工具pt-table-checksum--databasestesturoot,pxxx pt-table-sync--print--databasestesturoot,pxxx# 先 --print 看差异建议定期跑一次校验比如每周别等出问题才发现不一致。九、小结复制靠三个线程主库 dump 线程从库 IO 线程 SQL 线程binlog 必须用 ROW 格式STATEMENT 有NOW()这类不安全语句三种复制模式异步默认、半同步生产推荐、组复制搭建四步配 server-id → 建复制账号 → 导全量 → CHANGE MASTER START SLAVE两个 Yes 才正常看Last_IO_Error/Last_SQL_Error延迟主因是从库单线程回放→ 开多线程复制LOGICAL_CLOCK禁止大事务批量操作拆成小批Seconds_Behind_Master不完全可靠用pt-heartbeat更准读写分离第一大坑写完立刻读要强制走主库下一篇讲分库分表——主从解决读扩展但写压力和单表数据量到了瓶颈就必须把数据拆开了。
返回列表