在高并发的 Web 应用场景中,单一数据库服务器往往难以承受巨大的读写压力。读写分离 是一种经典的数据库架构优化方案:主库负责“写”操作,从库负责“读”操作,以此分担负载并提升系统可用性。
适用场景:
操作系统:Linux(CentOS / Ubuntu)
数据库版本:MySQL 8.0
服务端框架:Django
目标:实现读写分离、提高系统性能与可用性
一、为什么要使用 MySQL 主从架构? 在生产环境中,随着业务增长,数据库会逐渐成为性能瓶颈。常见问题:
主从架构(Master-Slave)可以解决:
问题
解决方式
读压力大
读写分离,查询走从库
数据安全
从库可做备份
高可用
主库异常可切换
扩展能力
可扩展多个从库
二、主从复制原理 MySQL 主从复制基于 binlog(二进制日志) 。
流程如下:
主库写入数据
主库记录 binlog
从库 IO 线程拉取主库 binlog
从库 SQL 线程执行 relay log
从库数据同步
结构示意:
Client ↓ Master(写 + binlog) ↓ Slave(relay log → 执行)
三、环境准备 1️⃣ 安装 MySQL 8.0(主从都需安装)
Ubuntu:
sudo apt update sudo apt install mysql-server ```` CentOS: ```bash yum install mysql-server
确认版本:
四、配置主库(Master) 假设:
主库IP:192.168.1.10
从库IP:192.168.1.20
1️⃣ 修改主库配置文件
编辑:
vim /etc/my.cnf vim /etc/mysql/mysql.conf.d/mysqld.cnf
在[mysqld]节点下添加以下配置(原有配置保留,新增如下内容):
[mysqld] server-id = 1 log-bin = mysql-binbinlog_format = mixedbinlog-do-db = django_project_dbbinlog-ignore-db = mysqlbinlog-ignore-db = information_schemaexpire_logs_days = 30 sync_binlog = 1
说明:
参数
说明
server-id
主从必须唯一
log-bin
开启 binlog
binlog_format
建议 ROW
binlog-do-db
指定需要同步的数据库(若不指定,同步所有数据库)
binlog-ignore-db
指定不需要同步的数据库(可选)
重启MySQL,使配置生效:
2️⃣ 创建复制账号
登录 MySQL:
CREATE USER 'repl_user' @'%' IDENTIFIED BY 'StrongP@ss123!' ; GRANT REPLICATION SLAVE ON * .* TO 'repl_user' @'%' ;FLUSH PRIVILEGES;
安全提示 :'%'允许任意IP连接,生产环境应替换为从库IP(如'repl_user'@'192.168.1.100')。
3️⃣ 查看主库状态
输出示例 :
+------------------+----------+--------------+------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | +------------------+----------+--------------+------------------+ | mysql-bin.000001 | 154 | | | +------------------+----------+--------------+------------------+
**记录File和Position**(后续配置从库必用)。
例如:
⚠️ 注意:此时不要关闭MySQL终端,也不要在主库执行任何写操作(如增删改),否则Position会变化,影响后续从库配置。
4️⃣ 导出主库现有数据(确保数据一致性)
mysqldump -u root -p --all-databases --single-transaction > /tmp/full_backup.sql
关键参数 :--single-transaction避免锁表,保证一致性。
也可以通过其他mysql远程工具导出某个数据库的数据(比如Navicat For MYSQL的转储SQL文件功能)。
五、配置从库(Slave)
1️⃣ 修改从库配置文件
[mysqld] server-id = 2 log-bin = OFF relay-log = mysql-relay-binread-only = 1 replicate-ignore-db = mysqlreplicate-ignore-db = information_schemareplicate-do-db = django_project_dbreplicate-ignore-table =history.userreplicate-wild-ignore-table = history.user
如果某个表数据不走从库,可以通过replicate-ignore-table =history.user忽略该表同步
重启:
2️⃣ 导入主库数据
导入所有数据
mysql -uroot -p < dump.sql
导入某个数据库
mysql -u 用户名 -p new_database < /path/to/backup.sql
3️⃣ 初始化主从关系
登录从库:
执行:
CHANGE MASTER TO MASTER_HOST= '主库IP' , MASTER_USER= 'repl' , MASTER_PASSWORD= 'repl123456' , MASTER_LOG_FILE= 'mysql-bin.000003' , MASTER_LOG_POS= 157 ;
启动复制:
4️⃣ 查看同步状态
重点关注:
Slave_IO_Running: Yes Slave_SQL_Running: Yes Seconds_Behind_Master: 0
说明:
字段
说明
Slave_IO_Running
IO线程是否正常
Slave_SQL_Running
SQL线程是否正常
Seconds_Behind_Master
延迟时间
若出现错误:
六、Django 配置读写分离
1️⃣ 配置多个数据库
编辑 settings.py:
DATABASES = { 'default' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'your_db' , 'USER' : 'root' , 'PASSWORD' : 'password' , 'HOST' : '主库IP' , 'PORT' : '3306' , }, 'slave' : { 'ENGINE' : 'django.db.backends.mysql' , 'NAME' : 'your_db' , 'USER' : 'root' , 'PASSWORD' : 'password' , 'HOST' : '从库IP' , 'PORT' : '3306' , } }
2️⃣ 创建数据库路由
新建:
project/utils/db_router.py
from .slave_switch import SLAVE_ENABLEDclass MasterSlaveDBRouter : READ_REPLICA_MODELS = { 'history_app.FigureList' , 'history_app.FigureEvent' , 'history_app.Wuqiannian' , 'history_app.WuqiannianDetail' , 'history_app.War' , 'history_app.TodayOnHistoryList' , 'history_app.HistoryCollectionList' } def db_for_read (self, model, **hints ): if not SLAVE_ENABLED: return 'default' model_key = f"{model._meta.app_label} .{model.__name__} " if model_key in self.READ_REPLICA_MODELS: if self.slave_available(): return 'slave' return 'default' def db_for_write (self, model, **hints ): return 'default' def allow_relation (self, obj1, obj2, **hints ): return True def allow_migrate (self, db, app_label, model_name=None , **hints ): return db == 'default' def slave_available (self ): """ 判断从库是否可用 """ try : from django.db import connections conn = connections['slave' ] conn.ensure_connection() return True except Exception: return False
其中SLAVE_ENABLED为是否开启从库开关;READ_REPLICA_MODELS为配置哪些模型走从库。
3️⃣ 注册路由
在settings.py中添加:
DATABASE_ROUTERS = ['your_app.routers.DatabaseRouter' ]
重启 Django 服务生效。
4️⃣ 测试Django项目读写分离
验证读写路由 :
obj = YourModel.objects.create(name="Test" ) objs = YourModel.objects.all ()
检查实际路由 (在Django shell中):
from django.db import connectionsprint (connections['default' ].settings_dict['HOST' ]) print (connections['replica' ].settings_dict['HOST' ])
输出示例 :
192.168.1.50 192.168.1.100
八、常见问题排查 配置过程中,最常见的问题是“从库同步失败”(Slave_IO_Running或Slave_SQL_Running为No),以下是高频问题及解决方案:
1️⃣ Slave_IO_Running: No
原因:从库无法连接主库,或主库同步用户权限不足、二进制日志文件名/位置错误。
# 解决方案: 1. 检查主从库网络连通性(从库ping主库IP,确保能ping通) ping 192.168.1.100 2. 检查主库同步用户权限(主库登录MySQL,查看权限) mysql -u root -p'MySql@123456' show grants for 'slave_user'@'192.168.1.101'; # 若权限不足,重新授权(参考主库2.4步骤) 3. 检查从库配置的主库二进制日志文件名/位置(与主库show master status输出一致) # 从库登录MySQL,查看配置 show slave status\G; # 对比MASTER_LOG_FILE和MASTER_LOG_POS与主库是否一致,不一致则重新配置(参考从库3.4步骤) stop slave; CHANGE MASTER TO ...; # 重新填写正确的File和Position start slave;
2️⃣ Slave_SQL_Running: No
原因:主从数据不一致(从库缺少主库的某些表/数据)、从库手动执行了写操作、SQL语句不兼容。
# 解决方案: 1. 重新同步主从数据(最常用,适合数据量不大的场景) # 主库重新导出数据,传输到从库,重新导入 mysqldump -u root -p'MySql@123456' --databases django_project_db > django_project_db.sql scp django_project_db.sql root@192.168.1.101:/root/ # 从库重新导入数据,重启同步 mysql -u root -p'MySql@123456' source /root/django_project_db.sql; stop slave; CHANGE MASTER TO ...; # 重新配置主库信息(确保File和Position正确) start slave; 2. 若从库手动执行了写操作,删除从库新增的数据,重启同步 # 从库登录MySQL,删除手动新增的数据,然后: stop slave; start slave;
3️⃣ Django项目无法连接主从库
原因:MySQL用户远程登录权限不足、防火墙未开放3306端口、数据库配置参数错误。
# 解决方案: 1. 检查MySQL用户远程权限(主从库均需配置,参考1.2.2步骤,允许Django项目所在IP连接) GRANT ALL PRIVILEGES ON *.* TO 'root'@'Django项目IP' IDENTIFIED BY 'MySql@123456' WITH GRANT OPTION; FLUSH PRIVILEGES; 2. 检查防火墙(确保3306端口开放,参考1.2.3步骤) 3. 检查Django settings.py中的数据库配置(HOST、PORT、USER、PASSWORD、NAME是否正确) 5.4 主库二进制日志丢失/过期 原因:主库配置了expire_logs_days,二进制日志过期删除,导致从库同步中断。 # 解决方案: 1. 调整主库my.cnf中的expire_logs_days参数,延长日志保存时间(如30天) expire_logs_days = 30 2. 重新同步主从数据