本文约 4,672 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:数据库成了瓶颈
站点做到一定规模(日 PV 十万以上、文章上万、插件几十个),常见现象:
- 数据库 CPU 长期 70%+;
- 慢查询日志堆积;
- 高峰期页面 TTFB 明显变慢;
- 升级服务器配置效果有限。
这时候有人会想到”读写分离”——主库写、从库读,把读压力分散出去。听起来很美,但 WordPress 上做这事有明确的坑,不是所有场景都适合。
原理分析:读写分离能解决什么
MySQL 主从复制(Replication):
主库(Master)──binlog──> 从库(Slave)
↑ 写入 ↑ 读取
- 所有写操作(INSERT/UPDATE/DELETE)走主库;
- 读操作(SELECT)走从库;
- 主库通过 binlog 异步同步给从库。
能解决的:读压力分散、备份可以在从库做、主库挂了可以切从库。
关键限制:复制是异步的,有延迟。
延迟通常是毫秒级,但以下情况会放大到秒级甚至分钟级:
- 主库有大事务(批量更新、ALTER TABLE);
- 从库机器性能差;
- 网络抖动;
- 从库还在追历史数据。
这个延迟在 WordPress 上会引发真实问题:
| 场景 | 表现 |
|---|---|
| 用户发帖后立即查看 | 从库还没同步,”文章不存在” |
| 提交评论后不显示 | 评论写入主库,读取从库,看不到自己的评论 |
| 后台改设置不生效 | 后台读从库,拿到旧值 |
| WooCommerce 下单 | 库存、订单状态不一致 |
| 缓存与数据库不一致 | 缓存更新了但读的是旧数据 |
核心矛盾:WordPress 的大多数请求是「读多写少」,但写入后的读必须立即可见(read-your-writes 一致性)。
保姆级实操
第一步:先评估是否真的需要
在考虑读写分离之前,先做完这些(投入产出比高得多):
- Redis 对象缓存:见WordPress Redis 对象缓存,能消除 60%–80% 的重复查询,通常就够了;
- 页面缓存:Nginx FastCGI 缓存配置,让请求根本不进 PHP;
- 慢查询优化:开慢日志,加索引;
wp_options的 autoload 清理:这是 WordPress 特有的性能杀手;
sql
-- 查看 autoload 总量
SELECT SUM(LENGTH(option_value))/1024/1024 AS 'MB', COUNT(*)
FROM wp_options WHERE autoload = 'yes';
超过 1MB 就该清理。
- 修订版本清理:见本站相关文章。
经验值:做完以上优化后,日 PV 50 万以下的内容站,单库通常能扛住。真到了撑不住的时候,读写分离也不是第一选择——分库分表或迁到托管数据库(如阿里云 RDS)更实际。
第二步:如果确实要做,用 HyperDB
WordPress 官方(Automattic)开发的 HyperDB 插件是唯一成熟的方案。它是个 drop-in(放在 wp-content/db.php),能按 SQL 语句类型路由到不同数据库。
基本架构:
┌─ Master (写) ─────────┐
WordPress ───────┤ ├─ 主从复制
└─ Slave1/2 (读) ───────┘
配置示例(db-config.php):
<?php
$wpdb->add_database( array(
'host' => '10.0.0.1', // 主库
'user' => 'wp',
'password' => 'xxxx',
'name' => 'wordpress',
'write' => 1, // 可写
'read' => 1, // 可读(优先级判断用)
) );
$wpdb->add_database( array(
'host' => '10.0.0.2', // 从库
'user' => 'wp',
'password' => 'xxxx',
'name' => 'wordpress',
'write' => 0, // 不可写
'read' => 1,
) );
// 关键:写入后的读仍走主库(保证 read-your-writes)
$wpdb->add_callback( 'mmf_read_after_write', 'mmf_callback' );
function mmf_callback( $query, $wpdb ) {
// ...
}
HyperDB 的关键机制:
- 按语句类型路由:
SELECT→ 从库;其他 → 主库; $wpdb->srtm(send reads to master):在一次请求内如果发生过写操作,后续读也走主库。这是解决延迟问题的核心;- 数据表级别路由:可以指定某张表只走主库(比如用户表、订单表)。
必须配置的”强制走主库”场景:
// 评论提交后
add_action( 'comment_post', function() {
global $wpdb;
$wpdb->srtm = true; // 本次请求剩余读操作走主库
} );
// 后台全部走主库
if ( is_admin() ) {
$wpdb->srtm = true;
}
// 登录用户走主库
if ( is_user_logged_in() ) {
$wpdb->srtm = true;
}
如果不开 srtm(或者登录用户/后台也走从库),必然出现前面列的那些一致性问题。
第三步:监控复制延迟
这是运维的必备动作:
-- 从库上执行
SHOW SLAVE STATUS\G
关注:
Slave_IO_Running: Yes、Slave_SQL_Running: Yes;Seconds_Behind_Master: 0(延迟秒数);Last_IO_Error/Last_SQL_Error(错误信息)。
告警脚本:
#!/bin/bash
DELAY=$(mysql -u root -p -e "SHOW SLAVE STATUS\G" | grep "Seconds_Behind_Master" | awk '{print $2}')
if [ "$DELAY" -gt 30 ] || [ "$DELAY" = "NULL" ]; then
echo "复制延迟 ${DELAY}s" | mail -s "MySQL 复制异常" admin@mimangfei.com
fi
延迟超过阈值时要自动把读切回主库——HyperDB 支持通过自定义 callback 读取延迟状态动态路由,但这需要较高的运维能力。
第四步:更务实的替代方案
| 方案 | 适用 | 复杂度 | 风险 |
|---|---|---|---|
| Redis 对象缓存 | 所有站点 | 低 | 低 ✅ |
| 页面缓存 | 内容站 | 低 | 低 ✅ |
| 升配 / 换 SSD | 所有站点 | 极低 | 无 ✅ |
| 托管数据库(RDS) | 有预算 | 低 | 低 ✅ |
| RDS 只读实例 | 读压力确实大 | 中 | 中 |
| 自建主从 + HyperDB | 有专职运维 | 高 | 高 |
| 分库分表 | 数据量极大 | 极高 | 高 |
我的建议排序:
- 先做缓存(Redis + 页面缓存)——90% 的站点到这里就够了;
- 换托管数据库(阿里云 RDS / 腾讯云 TDSQL),自带高可用、备份、监控;
- 如果 RDS 还不够,用云厂商提供的只读实例(有现成的代理层,自动处理路由和延迟),比自建主从 + HyperDB 靠谱得多;
- 自建主从 + HyperDB 是最后的选择,需要有人能实时监控延迟并处理故障。
阿里云 RDS 只读实例的用法:
- 在 RDS 控制台创建只读实例;
- 开启「数据库代理」,用读写分离地址连接;
- WordPress 里填代理地址即可,不需要 HyperDB;
- 代理层会自动处理”写后读”一致性(可配置为”一致性”或”弱一致性”)。
这是成本最低、风险最小的读写分离方案——不用改任何 WordPress 代码。
// wp-config.php
define( 'DB_HOST', 'proxy-xxxx.rwlb.rds.aliyuncs.com' ); // 读写分离代理地址
常见踩坑
- 延迟导致”发了文章看不到”:最典型的问题。必须用
srtm或让登录用户/后台全走主库。 - 从库数据不一致:如果有人在从库上写了数据(比如误连从库执行操作),主从会彻底不一致。从库必须设为
read_only=1:
sql
SET GLOBAL read_only = 1;
SET GLOBAL super_read_only = 1;
- 案例:某站做了读写分离后,用户注册后立即登录提示”用户不存在”。原因是注册写主库,登录读从库,延迟期间查不到。最后是给
wp_users表配了强制走主库才解决。 - 只做主从没做故障切换:主库挂了全站不可写,且 WordPress 会报”数据库连接失败”。主从不等于高可用,还需要 MHA / Orchestrator / 云厂商的自动切换。
- 备份在从库做但没校验:从库可能和主库不一致,备份出来的是错的。定期校验主从一致性(用
pt-table-checksum)。 - HyperDB 的 drop-in 冲突:
wp-content/db.php是数据库层的 drop-in,如果装了其他数据库类插件会冲突。 - 连接池打满:从库数量多了,每个都要占用连接。监控
Threads_connected和max_connections。 - 以为读写分离能解决慢查询:不能。慢查询该优化还得优化,从库只是把压力复制了一份。
总结
WordPress 读写分离技术上可行,但大多数站点不需要。正确顺序是:Redis 对象缓存 → 页面缓存 → 慢查询优化 → 升配/换 SSD → 托管数据库 → 云厂商只读实例,自建主从 + HyperDB 是最后的选项。如果真要做,三条铁律:从库设 read_only、后台和登录用户强制走主库(srtm)、实时监控 Seconds_Behind_Master 并告警。对于绝大多数独立站长,把精力放在缓存(WordPress Redis 对象缓存、Nginx FastCGI 缓存配置)和数据库清理上,收益远大于折腾主从架构。
