
1. 项目概述为什么我们需要主从复制与读写分离在任何一个业务量稍具规模的在线系统中数据库都是最核心、也最容易成为瓶颈的一环。想象一下你运营着一个电商网站白天有成千上万的用户同时浏览商品、下单、查询物流这些操作绝大部分都是对数据库的“读”请求。与此同时后台的运营人员在进行商品上架、价格调整、订单处理这些则是“写”操作。如果所有请求都涌向同一台数据库服务器会发生什么高峰期时CPU和I/O资源被争抢用户会明显感觉到页面加载变慢甚至出现超时错误体验极差。更危险的是一旦这台唯一的服务器因为硬件故障、机房断电或者一次错误的运维操作而宕机整个业务将瞬间停摆损失不可估量。MySQL主从复制Master-Slave Replication和读写分离Read-Write Splitting正是为了解决上述两个核心痛点而生的经典架构方案。这不是什么高深莫测的新技术而是经过无数互联网公司验证的、最基础也最有效的数据库高可用与性能扩展手段。简单来说主从复制就是让一台主数据库Master的数据自动、异步地同步到一台或多台从数据库Slave上。而读写分离则是在应用层面将“写”操作如INSERT, UPDATE, DELETE定向到主库将“读”操作如SELECT分摊到多个从库上。这套组合拳带来的好处是立竿见影的首先它极大地提升了系统的读并发能力因为读请求可以被多个从库分担其次它增强了数据可靠性即使主库宕机从库也能迅速顶替上来需要配合其他高可用方案再者它方便了数据备份、统计分析等离线操作可以在不影响主库性能的从库上进行。无论你是运维工程师、后端开发者还是架构师理解并能够亲手搭建这套环境都是一项不可或缺的核心技能。接下来我将抛开理论空谈带你从原理到实操一步步构建一个稳定可靠的MySQL主从复制与读写分离环境。2. 核心原理深度拆解日志、线程与数据流在动手配置之前我们必须吃透其工作原理。很多配置失败或数据不一致的问题根源都在于对原理的一知半解。MySQL的主从复制本质上是基于二进制日志Binary Log的异步数据同步。2.1 二进制日志复制的基石二进制日志binlog是MySQL服务层产生的一种逻辑日志它忠实地记录了所有对数据库执行更改的“事件”Event比如一条UPDATE语句影响了哪些行。与存储引擎层的重做日志redo log不同binlog是逻辑的、语句或行格式的主要用于复制和数据恢复。binlog的三种格式至关重要STATEMENTSBR记录原始的SQL语句。优点是日志量小节省空间和网络带宽。缺点是某些非确定性函数如NOW(),RAND(),UUID()或存储过程可能在主从库上执行结果不一致存在安全隐患。ROWRBR记录每一行数据被修改后的内容。优点是最安全能保证主从数据的绝对一致性。缺点是日志量巨大尤其是批量更新时可能对I/O和网络造成压力。MIXEDMBR混合模式。MySQL会自行判断对可能引起不一致的语句使用ROW格式其他使用STATEMENT格式。这是目前生产环境最推荐、也最常用的格式在安全性和性能之间取得了良好平衡。实操心得在my.cnf中通过binlog_format MIXED来设置。务必在主从库上都明确配置避免因默认值不同导致复制异常。早期版本默认可能是STATEMENT这是很多复制数据错误的源头。2.2 复制线程与工作流程主从复制主要由三个线程协同完成理解它们就像理解一条生产流水线Binlog Dump Thread主库当从库连接上主库时主库会为每个连接的从库创建一个“倾倒”线程。这个线程的唯一职责就是当主库的binlog有更新时主动将新增的日志事件“推”给从库的I/O线程。它就像流水线的源头投料工。I/O Thread从库从库上运行的线程负责与主库的Dump线程建立连接接收主库发送过来的binlog事件并将其写入从库本地的中继日志Relay Log文件中。它扮演着物流运输和临时仓储的角色。SQL Thread从库从库上另一个核心线程。它不停地读取本地的中继日志解析出其中记录的日志事件即当初在主库上执行的SQL语句或行变更并在从库上重新执行Replay一遍从而让从库的数据与主库保持一致。它就是流水线末端的组装工人。整个数据流可以概括为主库事务提交 - 写入Binlog - 主库Dump线程读取Binlog并发送 - 从库I/O线程接收并写入Relay Log - 从库SQL线程读取Relay Log并重放执行。2.3 异步复制与半同步复制默认的复制模式是完全异步的。主库事务提交后只要将事件写入自身binlog就认为成功并不关心从库是否收到或执行。这种模式性能最好但存在数据丢失风险如果主库在将事件发送给从库前崩溃那么已提交的事务数据可能丢失。为了解决这个问题MySQL 5.5引入了半同步复制Semisynchronous Replication。在半同步模式下主库在提交事务时会阻塞等待至少一个从库的I/O线程确认已收到该事件并写入其Relay Log注意不是执行完。收到确认后主库才返回给客户端事务提交成功。这在一定程度上保证了数据的安全性但以略微增加写延迟为代价。注意事项半同步复制虽然增强了数据可靠性但它不是强一致的。它只保证事件被从库接收不保证被执行。且如果超时时间内未收到从库确认主库会自动降级为异步模式以保证自身可用性。对于金融等强一致性场景需考虑MySQL Group Replication或基于Paxos/Raft的第三方方案。3. 环境准备与配置详解理论清晰后我们进入实战环节。假设我们有两台服务器192.168.1.100作为主库Master192.168.1.101作为从库Slave。操作系统均为CentOS 7MySQL版本为8.0版本需尽量一致避免兼容性问题。3.1 主库Master配置首先登录主库服务器编辑MySQL配置文件/etc/my.cnf路径可能因安装方式而异在[mysqld]段落下添加或修改以下关键参数[mysqld] # 服务器唯一ID这是主从识别的关键必须唯一 server-id 100 # 启用二进制日志并指定日志文件前缀 log-bin mysql-bin # 设置二进制日志格式推荐MIXED binlog_format MIXED # 设定需要复制的数据库可选不配置则默认复制所有库 # binlog-do-db your_database_name # 设定不需要复制的数据库可选与上一条二选一 # binlog-ignore-db mysql # binlog-ignore-db information_schema # binlog-ignore-db performance_schema # binlog-ignore-db sys # 为每个session分配的内存在事务过程中用来存储二进制日志的缓存 binlog_cache_size 1M # 设置二进制日志过期时间避免磁盘被占满单位天 expire_logs_days 7 # 跳过主从复制中遇到的所有错误或指定类型的错误避免复制中断 # 例如1062错误是主键重复1032错误是记录不存在。生产环境慎用建议设为0遇到错误手动处理。 slave_skip_errors 1062参数解读与避坑指南server-id这是整个复制拓扑中的“身份证”。主库、从库以及未来可能添加的级联从库都必须拥有全局唯一的ID。通常用IP地址的最后一段是个好习惯。log-bin不指定路径则默认存放在数据目录datadir下。务必确保该目录有足够的磁盘空间。binlog-ignore-db通常我们会忽略MySQL自带的系统库因为它们不需要被复制到从库且可能包含服务器特定的信息。这是必须配置的一步否则可能导致复制错误或从库系统表混乱。slave_skip_errors在初次搭建测试或处理一些可忽略的冲突时可以临时设置跳过特定错误。但在生产环境强烈建议设置为空或0让复制在出错时停止以便DBA及时介入排查根本原因防止数据不一致在沉默中扩散。配置完成后重启MySQL服务使配置生效systemctl restart mysqld3.2 创建复制专用账户出于安全考虑我们不应使用root账户进行复制。需要在主库上创建一个专门用于复制的用户。登录主库MySQL命令行mysql -u root -p执行以下SQL-- 创建一个用户名为‘repl’允许从‘192.168.1.101’主机连接的账户密码为‘Repl123456’ CREATE USER repl192.168.1.101 IDENTIFIED BY Repl123456; -- 授予该用户复制相关的权限 GRANT REPLICATION SLAVE ON *.* TO repl192.168.1.101; -- 刷新权限 FLUSH PRIVILEGES;重要安全提示密码应足够复杂且‘host’部分应严格限定为从库的IP地址不要使用‘%’通配符以减少安全风险。在生产环境中这一步的权限控制至关重要。3.3 获取主库状态信息在主库上执行以下命令记录下关键信息后续在从库配置时会用到SHOW MASTER STATUS;你会看到类似下面的输出------------------------------------------------------------------------------- | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | ------------------------------------------------------------------------------- | mysql-bin.000003 | 785 | | | | -------------------------------------------------------------------------------请务必记下File(mysql-bin.000003) 和Position(785)这两个值。它们告诉从库“请从我的这个日志文件的这个位置开始复制”。3.4 从库Slave配置现在登录从库服务器编辑其MySQL配置文件/etc/my.cnf[mysqld] # 服务器唯一ID必须与主库不同 server-id 101 # 启用中继日志 relay-log mysql-relay-bin # 将中继日志的索引文件也记录下来 relay-log-index slave-relay-bin.index # 设定需要复制的数据库可选与主库过滤规则配合使用 # replicate-do-db your_database_name # 设定不需要复制的数据库可选 # replicate-ignore-db mysql # replicate-ignore-db information_schema # replicate-ignore-db performance_schema # replicate-ignore-db sys # 设置为只读模式防止应用误写入从库导致数据不一致 read_only ON # 以下用户不受read_only限制例如复制线程、管理员 super_read_only ON关键点解析server-id必须唯一这里设为101。relay-log中继日志的文件名前缀。SQL线程就是读取这个日志来重放事件的。read_only和super_read_only这是保障从库数据纯洁性的重要屏障。设置为ON后普通用户连接从库只能执行读操作无法执行INSERT/UPDATE/DELETE。但具有SUPER权限的用户如复制线程repl依然可以写。这有效防止了运维或应用的误操作污染从库数据。同样配置完成后重启从库MySQL服务。3.5 从库关联主库并启动复制首先如果主库中已有存量数据我们需要先将这些数据同步到从库保证起点一致。最常用的方法是使用mysqldump进行逻辑备份并导入。在从库上执行# 1. 在主库执行备份注意排除系统库并使用--master-data参数记录binlog位置 # 在主库服务器上执行 mysqldump -u root -p --all-databases --master-data2 --flush-logs --ignore-tablemysql.gtid_executed --ignore-tablemysql.slave_relay_log_info --ignore-tablemysql.slave_master_info --ignore-tablemysql.slave_worker_info master_dump.sql # 2. 将备份文件传输到从库 scp master_dump.sql root192.168.1.101:/tmp/ # 3. 在从库服务器上导入数据 mysql -u root -p /tmp/master_dump.sql--master-data2参数会在导出的SQL文件中以注释形式包含CHANGE MASTER TO语句所需的MASTER_LOG_FILE和MASTER_LOG_POS非常方便。现在登录从库MySQL命令行执行关键的复制配置命令-- 停止从库复制线程如果是首次配置可能本来就是停止的 STOP SLAVE; -- 配置主库连接信息使用之前记录的File和Position CHANGE MASTER TO MASTER_HOST192.168.1.100, MASTER_USERrepl, MASTER_PASSWORDRepl123456, MASTER_PORT3306, MASTER_LOG_FILEmysql-bin.000003, MASTER_LOG_POS785, MASTER_CONNECT_RETRY30; -- 启动从库复制线程 START SLAVE;命令详解MASTER_HOST/PORT/USER/PASSWORD指向主库的连接信息。MASTER_LOG_FILE/POS这就是之前SHOW MASTER STATUS记下的值告诉从库从哪个点开始同步。如果使用了--master-data备份这个信息已经包含在备份文件里可以不用手动填写。MASTER_CONNECT_RETRY连接重试间隔秒网络不稳定时可适当调大。4. 状态检查与故障排查实战配置完成后不代表万事大吉。我们必须学会如何检查复制状态并具备基本的排错能力。4.1 检查复制状态在从库上执行SHOW SLAVE STATUS\G使用\G代替分号可以让结果以垂直格式显示更易读。在输出的大量信息中我们最需要关注以下两个字段Slave_IO_RunningI/O线程状态。必须是Yes表示正在从主库接收日志。Slave_SQL_RunningSQL线程状态。必须是Yes表示正在执行中继日志中的事件。Seconds_Behind_Master从库落后于主库的秒数。这是一个估算值。0表示完全同步NULL通常表示复制线程未运行一个持续较大的正数则意味着从库延迟严重需要关注。Last_IO_Error/Last_SQL_Error记录最近一次I/O或SQL线程的错误信息。正常运行时应为空。一个健康的复制状态输出中Slave_IO_Running和Slave_SQL_Running都应为Yes且Seconds_Behind_Master为一个较小的、相对稳定的数值如0或1。4.2 常见错误与解决方案实录在实际运维中复制中断是家常便饭。下面是我踩过坑后总结的几种典型错误及处理思路。问题一Slave_IO_Running: Connecting或Last_IO_Error: error connecting to master ...这表示从库的I/O线程无法连接到主库。排查网络使用ping和telnet master_ip 3306检查从库到主库的网络连通性和端口可达性。检查账户权限确认主库上为repl用户设置的host是否正确密码是否正确。可以在主库上尝试用此账户密码登录验证。检查防火墙确保主库服务器的3306端口对从库IP开放。问题二Slave_SQL_Running: No且Last_SQL_Error: Could not execute Write_rows event on table db.table; Duplicate entry X for key PRIMARY...这是经典的1062错误主键冲突。通常发生在以下几种情况从库曾被写入过数据比如在read_only未开启时应用误连从库做了插入。主库某条记录被删除后从库因为某种原因如slave_skip_errors跳过了删除事件之后主库又插入了相同主键的记录。备份恢复的数据与当前复制位点不匹配。处理流程谨慎操作确定数据以谁为准与业务方确认冲突的这条记录应该以主库的为准还是以从库的为准绝大多数情况以主库为准。临时跳过错误仅用于紧急恢复如果确定以主库为准可以临时跳过这个错误事件让复制继续。STOP SLAVE; SET GLOBAL SQL_SLAVE_SKIP_COUNTER 1; -- 跳过1个事件 START SLAVE;然后再次检查SHOW SLAVE STATUS\G。SQL_SLAVE_SKIP_COUNTER每次设置后会自动减1直到0。此方法治标不治本需后续根治。根治方法手动在从库上删除或修改那条冲突数据使其与主库一致或直接重新搭建复制。如果冲突数据多重新搭建可能是更干净的选择。问题三Slave_SQL_Running: No且Last_SQL_Error: Error executing row event: Cannot add or update a child row: a foreign key constraint fails外键约束失败。原因可能是从库上依赖的主表数据缺失或者主从执行顺序不一致导致暂时性的约束违反。处理思路检查从库上相关的外键关联表数据是否完整。对于复杂事务考虑在从库配置文件中设置slave_parallel_type LOGICAL_CLOCK和slave_parallel_workers 0来启用多线程复制但需注意这可能引入依赖问题。更根本的是审视数据库设计或确保应用逻辑不会导致此类问题。问题四Seconds_Behind_Master延迟持续很高这是复制延迟性能问题的集中体现。硬件瓶颈检查从库服务器的CPU、内存、磁盘I/O尤其是Relay Log和Data目录所在磁盘是否已成为瓶颈。从库硬件规格不应低于主库。单线程回放在MySQL 5.6之前SQL线程是单线程的主库并发写入高时从库串行回放必然延迟。解决方案是升级到5.6并开启并行复制。# 在从库my.cnf中配置 slave_parallel_type LOGICAL_CLOCK # 基于组提交的并行方式 slave_parallel_workers 4 # 设置并行工作线程数通常设置为CPU核心数大事务主库执行一个耗时很长的大事务如一次性更新百万行这个事务的binlog事件会在最后才写入。从库的Seconds_Behind_Master会显示为0直到主库提交然后瞬间变成一个很大的值。应避免在业务高峰期运行大事务将其拆分为小批量操作。长查询从库上如果有慢查询会阻塞SQL线程应用后续的日志。优化从库上的查询或将在从库上执行的统计、报表等重查询移到专门的离线分析库。5. 实现读写分离应用层与中间件方案主从复制搭建好后读写分离的实现就水到渠成了。关键在于如何让应用程序知道“写操作找主库读操作找从库”。主要有两种主流方案。5.1 应用层直连分离代码实现这是最直接、侵入性最强的方案。在应用程序的数据库连接层如使用JDBC、连接池进行判断。实现思路配置两个数据源一个指向主库写数据源一个或多个指向从库读数据源。在代码中根据要执行的SQL是读SELECT还是写INSERT/UPDATE/DELETE动态选择对应的数据源获取连接。对于事务内的操作为了数据一致性通常会让整个事务内的所有查询都走主库写数据源。以Spring Boot MyBatis为例的简化方案// 1. 配置多数据源 Configuration public class DataSourceConfig { Bean(name masterDataSource) ConfigurationProperties(prefix spring.datasource.master) public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } Bean(name slaveDataSource) ConfigurationProperties(prefix spring.datasource.slave) public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } Bean Primary public DataSource routingDataSource( Qualifier(masterDataSource) DataSource master, Qualifier(slaveDataSource) DataSource slave) { MapObject, Object targetDataSources new HashMap(); targetDataSources.put(DataSourceType.MASTER, master); targetDataSources.put(DataSourceType.SLAVE, slave); AbstractRoutingDataSource routingDataSource new AbstractRoutingDataSource() { Override protected Object determineCurrentLookupKey() { // 关键从线程上下文中获取数据源类型 return DataSourceContextHolder.getDataSourceType(); } }; routingDataSource.setDefaultTargetDataSource(master); routingDataSource.setTargetDataSources(targetDataSources); return routingDataSource; } } // 2. 使用AOP或注解拦截方法设置数据源类型 Aspect Component public class DataSourceAspect { Before(annotation(readOnly) || execution(* com..service..*.select*(..)) || execution(* com..service..*.get*(..)) || execution(* com..service..*.find*(..))) public void setReadDataSourceType(JoinPoint joinPoint) { // 如果是读方法切换到从库 DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE); } Before(execution(* com..service..*.insert*(..)) || execution(* com..service..*.update*(..)) || execution(* com..service..*.delete*(..)) || execution(* com..service..*.save*(..))) public void setWriteDataSourceType(JoinPoint joinPoint) { // 如果是写方法切换到主库 DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER); } After(execution(* com..service..*.*(..))) public void restoreDataSourceType(JoinPoint joinPoint) { // 方法执行完毕后清空上下文避免污染 DataSourceContextHolder.clearDataSourceType(); } }优缺点分析优点实现简单可控性强没有额外中间件开销。缺点代码侵入性强需要修改业务代码或增加大量AOP切面。维护困难数据源配置硬编码在应用中增减从库需要修改代码并重启。高可用处理复杂如果某个从库宕机需要应用层自己实现健康检查和故障转移逻辑。连接池管理复杂每个应用实例都需要维护多个连接池。5.2 使用数据库中间件推荐方案这是目前生产环境更主流、更优雅的方案。引入一个独立的代理层中间件应用程序像连接单点数据库一样连接这个代理由代理自动完成SQL解析、路由、结果聚合等复杂工作。主流中间件对比中间件特点适用场景MySQL RouterMySQL官方出品轻量级配置简单。主要做读写分离和故障转移。对功能要求简单希望与MySQL生态紧密集成的场景。ProxySQL功能强大高性能。支持查询规则、缓存、连接池、故障转移、负载均衡等。社区活跃。中大型项目需要精细化的SQL路由、缓存和监控。MyCat/ShardingSphere国产优秀中间件。除了读写分离更核心的功能是分库分表。功能全面但部署和配置相对复杂。数据量极大需要进行水平拆分的分布式数据库场景。以ProxySQL为例的快速部署安装可以从官网下载RPM包或源码编译安装。配置ProxySQL有分层配置系统内存层 - 运行时层 - 磁盘层。-- 登录ProxySQL管理界面默认端口6032 mysql -u admin -padmin -h 127.0.0.1 -P 6032 --promptProxySQL -- 1. 在后端服务器组中添加主从节点 INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (10, 192.168.1.100, 3306); -- 主库hostgroup 10 INSERT INTO mysql_servers(hostgroup_id, hostname, port) VALUES (20, 192.168.1.101, 3306); -- 从库hostgroup 20 -- 2. 配置监控用户ProxySQL用此用户检查后端MySQL状态 UPDATE global_variables SET variable_valuemonitor WHERE variable_namemysql-monitor_username; UPDATE global_variables SET variable_valuemonitor_password WHERE variable_namemysql-monitor_password; LOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK; -- 3. 配置应用访问的用户和路由规则 INSERT INTO mysql_users(username, password, default_hostgroup) VALUES (app_user, app_password, 10); -- 默认路由到主库组(10) -- 定义路由规则将SELECT语句路由到从库组(20)其他语句到主库组(10) INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (1, 1, ^SELECT.*FOR UPDATE, 10, 1); -- SELECT FOR UPDATE 是写操作走主库 INSERT INTO mysql_query_rules(rule_id, active, match_pattern, destination_hostgroup, apply) VALUES (2, 1, ^SELECT, 20, 1); -- 普通SELECT走从库 -- 4. 使配置生效 LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK; LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK; LOAD MYSQL QUERY RULES TO RUNTIME; SAVE MYSQL QUERY RULES TO DISK;应用连接将应用程序的数据库连接地址改为ProxySQL的地址默认端口6033用户名密码使用上面配置的app_user。中间件方案的巨大优势对应用透明应用无需任何修改像使用单库一样使用。动态管理可以随时在ProxySQL管理端增删后端数据库节点应用无感知。内置高可用中间件可以检测后端节点健康状态自动剔除故障节点实现故障转移。高级功能如查询缓存、流量控制、SQL审计等。个人体会对于绝大多数项目我强烈建议直接使用ProxySQL这类中间件方案。它把数据库集群的复杂度从应用层剥离出来让开发者更专注于业务逻辑。初期可能觉得配置有点复杂但一旦跑通后期的运维扩展会轻松很多。自己手写代码实现读写分离后期在连接池管理、故障切换、负载均衡策略上会踩无数的坑。6. 进阶考量与生产环境建议搭建一个能跑通的主从复制并不难但要使其稳定、高效地服务于生产环境还需要考虑更多。6.1 监控与告警“没有监控的系统就是在裸奔”。对于主从复制必须建立完善的监控体系。监控指标Slave_IO_Running/Slave_SQL_Running状态。Seconds_Behind_Master延迟时间。Slave_SQL_Running_StateSQL线程当前状态。主从库的Binlog文件大小和增长速率。主从库的服务器资源CPU、内存、磁盘空间、I/O。告警策略当复制线程状态异常、延迟超过阈值如30秒、磁盘空间不足时应立即通过邮件、短信、钉钉/企业微信机器人等渠道告警。可以使用 Prometheus Grafana 搭配mysqld_exporter来采集和展示这些指标并设置告警规则。6.2 备份与恢复策略主从架构为备份提供了极大便利。我们可以在从库上执行耗时很长的物理备份如Percona XtraBackup而完全不影响主库的线上服务。备份从库在从库上定期执行全量增量备份。恢复演练定期将备份文件恢复到测试环境验证备份的有效性和恢复流程。这是保证数据安全的生命线。延迟从库可以专门配置一个延迟若干小时如6小时的从库。当主库发生误操作如误删表时可以从这个延迟从库上找回未错误操作前的数据。6.3 一主多从与高可用架构单从库存在单点风险。生产环境通常采用一主多从架构。负载均衡多个从库可以更好地分担读压力。通过中间件如ProxySQL可以轻松实现读请求在多个从库间的负载均衡。高可用当主库宕机时需要将从库提升Promote为新的主库并让其他从库和应用程序指向新主库。这个过程称为故障切换Failover。手动操作风险高、速度慢。可以采用MHAMaster High Availability、Orchestrator等工具或使用InnoDB Cluster、Galera Cluster等MySQL原生集群方案来实现自动故障切换。6.4 数据一致性校验即使复制状态显示正常也不能100%保证主从数据完全一致。网络抖动、非事务引擎如MyISAM的使用、某些特殊SQL都可能导致数据不一致。需要定期进行数据一致性校验。工具推荐pt-table-checksum是 Percona Toolkit 中的神器。它可以在主库上运行通过计算数据块的校验和与从库进行比对找出不一致的数据。修复不一致找出不一致后可以使用pt-table-sync工具来修复从库上的数据使其与主库同步。注意这些操作对线上数据库有性能影响务必在业务低峰期进行并充分测试。搭建和维护MySQL主从复制与读写分离是一个从“能用”到“好用”再到“稳定”的持续过程。它不仅仅是运行几条配置命令更涉及到网络、服务器、数据库内核、应用架构和运维流程的方方面面。理解其原理掌握搭建和排错的方法并善用成熟的中间件和运维工具才能让这套经典的架构真正为你的业务系统保驾护航。