做Web开发,数据库是核心。随着网站流量增长,单台MySQL服务器很快就会成为瓶颈——读请求太多,数据库响应变慢;写操作锁定表,影响读性能;数据库挂了,整个网站就瘫了。
MySQL主从复制(Master-Slave Replication)是解决这些问题的最常用方案。它的原理是:主库(Master)负责写操作(INSERT、UPDATE、DELETE),从库(Slave)复制主库的数据,负责读操作(SELECT)。这样有几个好处:
第一,读写分离。读请求分发到从库,写请求留在主库,减轻主库的压力,提升数据库的并发能力。一般来说,Web应用的读操作远多于写操作(比例大约8:2甚至9:1),把读操作分散到多台从库,能大幅提升整体性能。
第二,数据备份。从库实时复制主库的数据,相当于一个实时的备份。主库的数据损坏或丢失,可以从从库恢复。而且可以在从库上做备份,不影响主库的性能。
第三,高可用。主库故障时,可以把一台从库提升为主库,继续提供服务,减少停机时间。配合负载均衡和自动故障转移,可以实现数据库的高可用。
第四,数据分析。可以在从库上做复杂的查询和数据分析,不影响主库的在线业务。
主从复制是MySQL最经典、最成熟的高可用方案,几乎所有中大型网站都在用。今天就来分享MySQL主从复制的原理和配置实战。
主从复制的原理
MySQL主从复制的原理是基于二进制日志(Binary Log,简称binlog)。主库把数据变更操作记录到binlog中,从库读取主库的binlog,然后在自己身上重放这些操作,从而实现数据同步。
具体过程分为三步:
- 主库记录binlog:主库在执行完数据变更操作(INSERT、UPDATE、DELETE、CREATE等)后,把这些操作记录到binlog文件中。binlog有三种格式:STATEMENT(记录SQL语句)、ROW(记录行的变化)、MIXED(混合模式,默认用STATEMENT,特殊情况自动切换到ROW)。
- 从库IO线程读取binlog:从库启动一个IO线程,连接到主库,请求读取binlog。主库启动一个binlog dump线程,把binlog的内容发送给从库。从库IO线程把接收到的binlog写入到自己的中继日志(Relay Log)中。
- 从库SQL线程重放中继日志:从库启动一个SQL线程,读取中继日志中的内容,在从库上重放这些操作,从而实现数据同步。
这个过程是异步的——主库不需要等待从库复制完成,就可以继续处理请求。所以主从复制有一定的延迟(通常在毫秒到秒级),从库的数据不是实时的,而是"准实时"的。对于大多数应用来说,这个延迟可以接受;但对于要求强一致性的场景(如支付、库存),读操作应该走主库。
配置前的准备
在配置主从复制之前,需要做一些准备工作:
- 主库和从库的MySQL版本:建议主从版本一致,或者从库版本比主库高(从库可以兼容低版本主库的binlog,但反过来不行)。本文以MySQL 5.6为例。
- 服务器之间网络互通:主库和从库之间要能互相访问,主库的3306端口要对从库开放。
- 主库开启binlog:主库必须开启binlog,并且设置server-id。
- 数据一致性:配置主从之前,主库和从库的数据应该一致。如果主库已经有数据,需要先把主库的数据导出,导入到从库,然后再配置复制。
主库配置
首先配置主库(Master)。编辑主库的MySQL配置文件my.cnf(Linux下通常在/etc/my.cnf或/etc/mysql/my.cnf):
[mysqld]
# 服务器ID,主从必须不同,主库设为1
server-id = 1
# 开启binlog
log-bin = mysql-bin
# binlog格式,推荐ROW或MIXED
binlog_format = mixed
# 同步的数据库(如果不设置,默认同步所有数据库)
# binlog-do-db = mydb
# 不同步的数据库
binlog-ignore-db = mysql
binlog-ignore-db = information_schema
binlog-ignore-db = performance_schema
# 中继日志(从库需要,主库也可以设置)
relay-log = relay-bin
# 字符集
character-set-server = utf8mb4配置完成后,重启MySQL:
service mysql restart然后在主库上创建一个用于复制的用户,从库用这个用户连接主库:
-- 创建复制用户
CREATE USER 'repl'@'%' IDENTIFIED BY 'repl_password';
-- 授予复制权限
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
-- 刷新权限
FLUSH PRIVILEGES;接下来,锁表并导出主库数据(如果主库已经有数据):
-- 锁表,防止数据变更
FLUSH TABLES WITH READ LOCK;
-- 查看主库状态,记录File和Position的值
SHOW MASTER STATUS;记录下SHOW MASTER STATUS的输出,比如:
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 | 120 | | mysql |
+------------------+----------+--------------+------------------+File是mysql-bin.000001,Position是120,这两个值后面配置从库时要用。
然后在另一个终端导出主库数据:
mysqldump -u root -p --all-databases --master-data > all_db.sql导出完成后,解锁表:
UNLOCK TABLES;从库配置
接下来配置从库(Slave)。编辑从库的MySQL配置文件:
[mysqld]
# 服务器ID,必须和主库不同,设为2
server-id = 2
# 从库也可以开启binlog(用于级联复制或备份)
log-bin = mysql-bin
# 中继日志
relay-log = relay-bin
# 只读模式(从库设为只读,防止误写)
read-only = 1
# 字符集
character-set-server = utf8mb4注意:read-only参数只对普通用户生效,root用户和有SUPER权限的用户仍然可以写。如果要完全禁止写,可以用super-read-only(MySQL 5.7+支持)。
配置完成后,重启MySQL:
service mysql restart然后把主库导出的数据导入到从库:
mysql -u root -p < all_db.sql导入完成后,在从库上配置复制,连接主库:
CHANGE MASTER TO
MASTER_HOST='主库IP地址',
MASTER_USER='repl',
MASTER_PASSWORD='repl_password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=120;MASTERLOGFILE和MASTERLOGPOS就是之前在主库上SHOW MASTER STATUS得到的值。
然后启动从库复制:
START SLAVE;查看从库复制状态:
SHOW SLAVE STATUS\G重点看这两个值:
- SlaveIORunning: Yes
- SlaveSQLRunning: Yes
如果两个都是Yes,说明复制正常运行。如果有一个是No,说明复制出错了,需要查看LastIOError或LastSQLError的错误信息,排查问题。
验证主从复制
配置完成后,验证一下主从复制是否正常工作。
在主库上创建一个测试表,插入一条数据:
CREATE DATABASE test_repl;
USE test_repl;
CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
INSERT INTO users (name) VALUES ('test1');然后在从库上查询:
USE test_repl;
SELECT * FROM users;如果能查到刚才插入的数据,说明主从复制正常工作。
再测试更新和删除:
-- 主库
UPDATE users SET name = 'test2' WHERE id = 1;
DELETE FROM users WHERE id = 1;从库上应该能同步看到更新和删除的结果。
读写分离配置
主从复制配置好之后,就可以实现读写分离了——写操作走主库,读操作走从库。
读写分离有几种实现方式:
- 应用层实现:在代码中判断SQL类型,SELECT走从库,INSERT/UPDATE/DELETE走主库。可以用数据库中间件(如MyCat、Atlas),或者在代码中用多个数据源。
- 代理层实现:用MySQL代理(如ProxySQL、MaxScale),自动解析SQL,路由到对应的数据库。应用只需要连接代理,不需要关心读写分离。
- 负载均衡器实现:用HAProxy或LVS做负载均衡,读请求分发到从库,写请求发到主库。但这种方式不能解析SQL,需要配置端口或规则区分。
对于PHP应用,最简单的方式是在代码中用两个数据库连接——一个主库连接(用于写),一个从库连接(用于读)。示例代码:
<?php
// 主库连接(写操作)
$master = new PDO('mysql:host=主库IP;dbname=mydb;charset=utf8mb4', 'user', 'pass');
// 从库连接(读操作)
$slave = new PDO('mysql:host=从库IP;dbname=mydb;charset=utf8mb4', 'user', 'pass');
// 写操作走主库
$stmt = $master->prepare("INSERT INTO users (name) VALUES (?)");
$stmt->execute(['test']);
// 读操作走从库
$stmt = $slave->query("SELECT * FROM users");
$users = $stmt->fetchAll();如果有多个从库,可以在从库之间做负载均衡,把读请求分散到不同的从库。
需要注意的是,对于要求强一致性的读操作(如刚写完就查询、支付查询、库存查询),应该走主库,因为主从复制有延迟,从库可能还没同步到最新数据。
常见问题
第一,SlaveIORunning: No。IO线程没启动,通常是因为:主库连接信息错误(IP、端口、用户名、密码)、主库的binlog文件或位置错误、网络不通、主库的防火墙没开放3306端口、复制用户权限不对。排查方法:检查CHANGE MASTER TO的参数是否正确、测试从库能否连接主库(mysql -h主库IP -u repl -p)、查看LastIOError的错误信息。
第二,SlaveSQLRunning: No。SQL线程出错,通常是因为:主从数据不一致(从库上有主库没有的数据或表结构)、SQL语句在从库执行失败(如主键冲突、表不存在)、binlog格式问题。排查方法:查看LastSQLError的错误信息、根据错误信息修复从库数据、然后用SET GLOBAL SQLSLAVESKIP_COUNTER = 1跳过错误事务,再START SLAVE。如果错误很多,建议重新配置主从(导出主库数据,重新导入从库)。
第三,主从延迟大。从库同步慢,延迟高。原因可能是:从库性能差(CPU、内存、磁盘IO不足)、网络带宽不够、主库写操作太多、从库上有慢查询占用资源、单线程复制(MySQL 5.6之前从库是单线程复制,5.6+支持多线程复制)。优化方法:提升从库硬件性能、优化网络、在从库上优化慢查询、开启多线程复制(MySQL 5.6+,设置slaveparallelworkers > 0)。
第四,主库故障切换。主库挂了,需要把从库提升为主库。步骤:停止从库复制(STOP SLAVE)、重置从库(RESET MASTER)、修改应用配置连接新主库、其他从库重新指向新主库(CHANGE MASTER TO)。这个过程可以手动操作,也可以用工具(如MHA、Orchestrator)实现自动故障转移。
总结
MySQL主从复制是最常用、最成熟的数据库高可用和性能优化方案。它基于binlog实现数据同步,配置简单,稳定可靠。通过主从复制,可以实现读写分离、数据备份、高可用、数据分析等功能。
如果你的网站流量增长,单台MySQL扛不住了,不妨试试主从复制。它能帮你提升数据库性能,保障数据安全,实现高可用。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录