搜索全站
数据库 12 分钟阅读

WordPress 数据库只读副本与读写分离的可行性分析

流量大了数据库扛不住,能不能搞读写分离?本文客观分析 WordPress 做主从复制的可行性:能解决什么、解决不了什么、延迟带来的坑,以及更务实的替代方案。

U
UFO_PF 2018-12-13 · 阅读 12 分钟
Wo 数据库
阅读提示
本文约 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 一致性)。

保姆级实操

第一步:先评估是否真的需要

在考虑读写分离之前,先做完这些(投入产出比高得多):

  1. Redis 对象缓存:见WordPress Redis 对象缓存,能消除 60%–80% 的重复查询,通常就够了;
  2. 页面缓存:Nginx FastCGI 缓存配置,让请求根本不进 PHP;
  3. 慢查询优化:开慢日志,加索引;
  4. wp_options 的 autoload 清理:这是 WordPress 特有的性能杀手;

sql
-- 查看 autoload 总量
SELECT SUM(LENGTH(option_value))/1024/1024 AS 'MB', COUNT(*)
FROM wp_options WHERE autoload = 'yes';

超过 1MB 就该清理。

  1. 修订版本清理:见本站相关文章。

经验值:做完以上优化后,日 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 的关键机制:

  1. 按语句类型路由:SELECT → 从库;其他 → 主库;
  2. $wpdb->srtm(send reads to master):在一次请求内如果发生过写操作,后续读也走主库。这是解决延迟问题的核心;
  3. 数据表级别路由:可以指定某张表只走主库(比如用户表、订单表)。

必须配置的”强制走主库”场景:

// 评论提交后
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 有专职运维 高 高
分库分表 数据量极大 极高 高

我的建议排序:

  1. 先做缓存(Redis + 页面缓存)——90% 的站点到这里就够了;
  2. 换托管数据库(阿里云 RDS / 腾讯云 TDSQL),自带高可用、备份、监控;
  3. 如果 RDS 还不够,用云厂商提供的只读实例(有现成的代理层,自动处理路由和延迟),比自建主从 + HyperDB 靠谱得多;
  4. 自建主从 + HyperDB 是最后的选择,需要有人能实时监控延迟并处理故障。

阿里云 RDS 只读实例的用法:

  • 在 RDS 控制台创建只读实例;
  • 开启「数据库代理」,用读写分离地址连接;
  • WordPress 里填代理地址即可,不需要 HyperDB;
  • 代理层会自动处理”写后读”一致性(可配置为”一致性”或”弱一致性”)。

这是成本最低、风险最小的读写分离方案——不用改任何 WordPress 代码。

// wp-config.php
define( 'DB_HOST', 'proxy-xxxx.rwlb.rds.aliyuncs.com' );  // 读写分离代理地址

常见踩坑

  1. 延迟导致”发了文章看不到”:最典型的问题。必须用 srtm 或让登录用户/后台全走主库。
  2. 从库数据不一致:如果有人在从库上写了数据(比如误连从库执行操作),主从会彻底不一致。从库必须设为 read_only=1:

sql
SET GLOBAL read_only = 1;
SET GLOBAL super_read_only = 1;

  1. 案例:某站做了读写分离后,用户注册后立即登录提示”用户不存在”。原因是注册写主库,登录读从库,延迟期间查不到。最后是给 wp_users 表配了强制走主库才解决。
  2. 只做主从没做故障切换:主库挂了全站不可写,且 WordPress 会报”数据库连接失败”。主从不等于高可用,还需要 MHA / Orchestrator / 云厂商的自动切换。
  3. 备份在从库做但没校验:从库可能和主库不一致,备份出来的是错的。定期校验主从一致性(用 pt-table-checksum)。
  4. HyperDB 的 drop-in 冲突:wp-content/db.php 是数据库层的 drop-in,如果装了其他数据库类插件会冲突。
  5. 连接池打满:从库数量多了,每个都要占用连接。监控 Threads_connected 和 max_connections。
  6. 以为读写分离能解决慢查询:不能。慢查询该优化还得优化,从库只是把压力复制了一份。

总结

WordPress 读写分离技术上可行,但大多数站点不需要。正确顺序是:Redis 对象缓存 → 页面缓存 → 慢查询优化 → 升配/换 SSD → 托管数据库 → 云厂商只读实例,自建主从 + HyperDB 是最后的选项。如果真要做,三条铁律:从库设 read_only、后台和登录用户强制走主库(srtm)、实时监控 Seconds_Behind_Master 并告警。对于绝大多数独立站长,把精力放在缓存(WordPress Redis 对象缓存、Nginx FastCGI 缓存配置)和数据库清理上,收益远大于折腾主从架构。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。