WordPress Multisite 不是多站点:可扩展到 500 个位置的架构
WordPress Multisite 不是真正的多站点:可扩展到 500 个位置的架构
WordPress Multisite 听起来像是你会得到多个站点。但实际上你得到的是一个 WordPress 安装,通过在数据库表名前面添加数字来假装是多个站点。你的主站点使用 wp_posts。子站点 2 使用 wp_2_posts。子站点 3 使用 wp_3_posts。这不是多站点架构。这是一个数据库,其中包含相同表的编号副本。这会产生后果。
我曾将拥有 15 个、40 个,以及一次 87 个子站点的网络从 WordPress Multisite 迁移出来。每一次,客户都以为他们得到的是独立站点。每一次,他们都以艰难的方式发现他们并没有。如果你在 WordPress Multisite 上运行特许经营、多地点业务或大学部门网络,这篇文章会有点刺痛。但现在知道总比在子站点 #47 导致所有其他 46 个站点宕机后才知道要好。
目录
- WordPress Multisite 在幕后实际做什么
- 假多站点架构的 7 个后果
- WordPress Multisite 何时可行(坦诚相待)
- 实际可扩展到 500 个位置的架构
- 架构比较:前缀表 vs 行级安全
- 实现:Next.js + Supabase 用于多位置站点
- 迁移路径:从 WordPress Multisite 迁出
- 真实数字:性能和成本比较
- 常见问题解答

WordPress Multisite 在幕后实际做什么
让我们看看当你在 WordPress 中启用 Multisite 时发生了什么。你在 wp-config.php 中添加几行代码:
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);
define('DOMAIN_CURRENT_SITE', 'example.com');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);
WordPress 然后创建几个网络级表(wp_blogs、wp_site、wp_sitemeta、wp_registration_log、wp_signups),并开始为每个新子站点复制你的核心表结构。
下面是仅有 5 个子站点后数据库的样子:
wp_posts (主站点)
wp_postmeta (主站点)
wp_options (主站点)
wp_users (共享 - 所有站点)
wp_usermeta (共享 - 所有站点)
wp_2_posts (子站点 2)
wp_2_postmeta (子站点 2)
wp_2_options (子站点 2)
wp_3_posts (子站点 3)
wp_3_postmeta (子站点 3)
wp_3_options (子站点 3)
...
wp_5_posts (子站点 5)
wp_5_postmeta (子站点 5)
wp_5_options (子站点 5)
五个子站点,你已经有大约 55 个表。五十个子站点?你会看到单个 MySQL 数据库中超过 500 个表。五百个位置?北部超过 5,000 个表。在一个数据库中。共享一个连接池。
每个表都有自己的索引。每个查询都针对特定的前缀表。查询规划器必须处理所有这些。而且这些表中的每一个都可以被运行在该服务器上的任何 PHP 进程访问,因为它们都在同一个数据库中,使用相同的凭证。
这不是多站点架构。这是冒充隔离的命名约定。
假多站点架构的 7 个后果
1. 共享漏洞表面
WordPress Multisite 网络中的每个子站点都运行相同的 WordPress 核心、相同的插件和相同的主题。一个插件漏洞会危及所有子站点,因为它们共享相同的代码库。
这不是理论。在 2026 年 2 月,WPVivid——一个拥有超过 900,000 个活跃安装的备份插件——披露了一个严重级别为 9.8 的 RCE(远程代码执行)漏洞。严重级别为 10 分中的 9.8 分。这是"未经身份验证的攻击者可以在你的服务器上执行任意代码"的范畴。
在独立 WordPress 站点上,那是一个受危害的站点。在拥有 30 个子站点的 Multisite 网络上?那是 30 个受危害的站点。相同的服务器。相同的数据库。相同的文件系统。一个漏洞,整个网络受到威胁。
你不能在子站点 #12 上安装不同版本的插件,而在子站点 #13 上安装不同版本。你不能将一个位置的插件沙盒与另一个位置隔离开来。每个站点都获得相同的攻击表面。
2. 插件冲突倍增
在单个 WordPress 站点上,插件冲突会破坏一个站点。你停用插件,诊断问题,继续前进。令人恼火但可管理。
在 Multisite 上,网络激活的插件冲突会破坏每个站点。我见过一次 WooCommerce 更新在 Multisite 网络上击倒 23 个位置页面,因为网络激活的缓存插件还没有针对新的 WooCommerce 钩子进行更新。二十三个位置的页面损坏了。一个根本原因,二十三个生气的位置管理员打给同一个人。
这里的妙处是——个人站点管理员通常无法停用网络激活的插件。他们必须等待超级管理员来修复它。
3. 性能下降
五十个子站点共享一个 MySQL 实例。子站点 #47 上的每个页面加载都运行如下查询:
SELECT * FROM wp_47_posts WHERE post_type = 'page' AND post_status = 'publish';
SELECT option_value FROM wp_47_options WHERE option_name = 'active_plugins';
SELECT * FROM wp_47_postmeta WHERE post_id IN (142, 143, 144, 145);
同时,子站点 #12 针对 wp_12_posts、wp_12_options 和 wp_12_postmeta 运行自己的一组查询。每个其他子站点也是如此,所有这些都在同一 MySQL 实例上。
MySQL 的查询规划器变得困惑。表缓存填满。每个前缀表都维护自己的索引,但缓冲池是共享的。随着你添加子站点,性能非线性下降。从 10 到 20 个子站点不是两倍的负载——取决于流量模式,可能是三倍或四倍的负载,因为锁争用和缓冲池颠簸。
我曾对一个 40 子站点网络进行基准测试。子站点 #1 上的平均查询时间是 45ms。当我们测试子站点 #38 时,平均查询时间攀升到 380ms。相同的查询结构。每个站点相同的数据量。数据库只是淹没在表中。
4. 迁移是一场噩梦
尝试将子站点 #23 从 50 站点网络提取到其自己的独立 WordPress 安装。这是你需要做的事:
- 导出所有
wp_23_前缀的表 - 将每个表从
wp_23_前缀重新映射到wp_前缀 - 重新序列化所有选项和小部件数据(WordPress 在数据库中存储序列化的 PHP,当你改变前缀时字符串长度会改变)
- 将媒体路径从
/uploads/sites/23/重新映射到/uploads/ - 搜索和替换所有内部 URL
- 在
wp_usermeta中重新映射用户功能从wp_23_capabilities到wp_capabilities - 从共享的
wp_users表中提取用户(仅属于子站点 #23 的用户) - 重新创建用户到站点的关系
序列化中的一个错误,你得到损坏的数据。小部件设置消失。主题定制器选项破坏。菜单结构消失。我做过几十次这个提取过程,即使使用像 WP Migrate DB Pro 这样的工具,每个子站点也需要 4-8 小时。如果你正在停用一个网络,将其乘以 50 个子站点。
WordPress 生态系统对此有工具,当然。但这些工具需要存在的事实告诉你一些关于这个架构的东西。
5. 没有真正的数据隔离
如果你关心安全性或合规性,这就是应该让你感到恐惧的那个。
子站点 #2 上的 SQL 注入漏洞不仅仅暴露 wp_2_posts。连接到 MySQL 的数据库用户可以访问数据库中的每个表。这意味着 wp_posts(主站点)、wp_3_posts(子站点 3)、wp_users(所有站点的所有用户)以及所有其他表。
没有数据库级隔离。没有行级安全。没有模式分离。WordPress Multisite 是一个数据库、一组凭证和一个命名约定。就是这样。
对于在各个位置处理客户数据的业务——医疗办公室、律师事务所、金融服务——这是一个合规问题。HIPAA、SOC 2 和 PCI DSS 都对数据隔离有要求。"我们使用不同的表前缀"不会让审计员满意。
6. 超级管理员瓶颈
WordPress Multisite 有一个名为"超级管理员"的角色。只有超级管理员才能:
- 安装或删除插件
- 安装或删除主题
- 网络范围内激活插件
- 向网络添加新站点
- 管理网络设置
个人站点管理员的功能受到限制。他们不能安装自己的插件。他们不能添加自己的主题。任何影响共享基础设施的更改都会通过一个人(或一个小团队)路由。
对于一个 3 站点网络,这很好。对于一个 50 位置特许经营,其中每个位置管理员想添加自己的预订小部件或菜单 PDF 插件?这是一个产生怨恨和影子 IT 的瓶颈。
7. 域映射脆弱
想要每个位置都有自己的域?denver.yourbrand.com 或 yourbrand-denver.com?WordPress Multisite 不以可靠的方式本地处理这个问题。你需要一个域映射解决方案,而内置的 sunrise.php dropin 方法是臭名昭著的不可靠。
为映射的域的 SSL 证书添加了另一层。DNS 配置添加另一个。每个映射的域都是你的超级管理员必须管理的另一个潜在故障点。一个 DNS 更改、一个过期的证书、一个错误配置的映射条目,位置的站点就会下线。
WordPress Multisite 何时可行(坦诚相待)
我不会假装它没有用。WordPress Multisite 在特定场景中运行良好:
- 小型网络(少于 10 个站点),其中所有站点由同一团队管理
- 大学部门站点,其中集中式控制实际上是期望的
- 开发/暂存/生产镜像用于同一项目
- 博客网络,其中内容隔离不是关键
如果你有 5-8 个站点、一个有能力的系统管理员,并且你不需要站点之间的数据隔离——Multisite 很好。问题从你尝试将其扩展到几十或数百个位置时开始。

实际可扩展到 500 个位置的架构
这是我们在 Social Animal 为多位置业务使用的替代方法:一个由 Next.js 前端和 Supabase(PostgreSQL)后端组成的无头架构,使用行级安全 (RLS) 实现真正的数据隔离。
核心思想:不是为每个位置复制表,而是有一组表,其中有 location_id 列。数据库级安全策略确保查询仅返回授权位置的数据。而不是 500 个独立的 WordPress 安装假装彼此独立,你有一个应用程序部署,其中 /locations/[slug] 从数据库行动态呈现每个位置的页面。
行级安全实际如何工作
-- 创建位置表
CREATE TABLE locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
address TEXT,
phone TEXT,
hours JSONB,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 创建带有位置隔离的页面表
CREATE TABLE pages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
location_id UUID REFERENCES locations(id),
title TEXT NOT NULL,
content JSONB,
slug TEXT NOT NULL,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- 启用 RLS
ALTER TABLE pages ENABLE ROW LEVEL SECURITY;
-- 策略:位置管理员只能看到自己位置的页面
CREATE POLICY "location_isolation" ON pages
FOR ALL
USING (location_id = (SELECT location_id FROM user_locations WHERE user_id = auth.uid()));
-- 策略:公众可以读取任何位置的已发布页面
CREATE POLICY "public_read" ON pages
FOR SELECT
USING (published = true);
使用此设置,丹佛的位置管理员即使以某种方式制造恶意查询,在物理上也无法访问奥斯汀的数据。数据库强制执行它。不是应用程序层。不是命名约定。数据库本身。
架构比较:前缀表 vs 行级安全
这里是两种架构的可视化表示:
WordPress Multisite 架构:
┌─────────────────────────────────────────────┐
│ 单一 MySQL 数据库 │
│ │
│ wp_posts (主站点) │
│ wp_options (主站点) │
│ wp_2_posts (丹佛) │
│ wp_2_options (丹佛) │
│ wp_3_posts (奥斯汀) │
│ wp_3_options (奥斯汀) │
│ wp_4_posts (西雅图) │
│ wp_4_options (西雅图) │
│ ... x 500 个位置 = 5,000+ 个表 │
│ │
│ ⚠️ 一组数据库凭证 │
│ ⚠️ 一个 PHP 进程访问所有表 │
│ ⚠️ 零数据库级隔离 │
└─────────────────────────────────────────────┘
Next.js + Supabase 架构:
┌─────────────────────────────────────────────┐
│ 单一 PostgreSQL 数据库 │
│ │
│ locations (500 行,每个位置一行) │
│ pages (每个位置的内容) │
│ media (每个位置的图像) │
│ staff (每个位置的团队) │
│ reviews (每个位置的评论) │
│ │
│ ✅ RLS 策略强制隔离 │
│ ✅ 丹佛用户无法查询奥斯汀数据 │
│ ✅ 总共 5 个表,不是 5,000 个 │
│ ✅ 标准索引,最优查询计划 │
└─────────────────────────────────────────────┘
在两种情况下都是一个数据库。但隔离模型是根本不同的。RLS 在 PostgreSQL 引擎级别强制执行——这不是应用程序可以绕过的东西。
| 因素 | WordPress Multisite | Next.js + Supabase |
|---|---|---|
| 500 个位置处的表数 | ~5,500+ | ~5-15 |
| 数据隔离 | 无(仅命名约定) | 数据库强制 RLS |
| 漏洞表面 | 一个漏洞 = 所有站点 | 每位置身份验证隔离 |
| 插件冲突 | 网络范围停机 | 不适用(无插件架构) |
| 添加位置 | 创建子站点 + 配置 | 插入数据库行 |
| 删除位置 | 复杂的提取过程 | 用 CASCADE 删除行 |
| 域映射 | 需要插件,脆弱 | 中间件重写,原生 |
| 构建/部署时间 | 不适用(运行时 PHP) | ~30 秒增量构建 |
| TTFB(平均,未缓存) | 800ms-2s+ | 50-150ms(边缘) |
| 500 个站点的每月托管费 | $200-800/月(托管 WP) | $25-75/月(Supabase + Vercel) |
| 从泄露恢复 | 完整网络修复 | 轮换密钥,重新部署 |
实现:Next.js + Supabase 用于多位置站点
这是使用 Next.js 实际路由的工作方式:
// app/locations/[slug]/page.tsx
import { createClient } from '@/lib/supabase/server';
import { notFound } from 'next/navigation';
export async function generateStaticParams() {
const supabase = createClient();
const { data: locations } = await supabase
.from('locations')
.select('slug');
return locations?.map(({ slug }) => ({ slug })) ?? [];
}
export default async function LocationPage({
params
}: {
params: { slug: string }
}) {
const supabase = createClient();
const { data: location } = await supabase
.from('locations')
.select(`
*,
pages(*),
staff(*),
reviews(*, rating)
`)
.eq('slug', params.slug)
.single();
if (!location) notFound();
return (
<div>
<h1>{location.name}</h1>
<LocationHero location={location} />
<LocationServices pages={location.pages} />
<LocationTeam staff={location.staff} />
<LocationReviews reviews={location.reviews} />
</div>
);
}
添加新位置?插入一行:
INSERT INTO locations (slug, name, address, phone, hours)
VALUES (
'portland-or',
'Portland, OR',
'123 Burnside St, Portland, OR 97209',
'(503) 555-0142',
'{"mon": "9-5", "tue": "9-5", "wed": "9-5"}'
);
就是这样。下一个构建会捕捉到它。或者如果你在使用 ISR(增量静态再生成),它会在你的重新验证窗口内出现,而无需构建。
删除位置?DELETE FROM locations WHERE slug = 'portland-or'; 级联删除会处理其余的。没有 4-8 小时的提取过程。
对于每个位置的自定义域,Next.js 中间件干净地处理它:
// middleware.ts
import { NextResponse } from 'next/server';
const DOMAIN_MAP: Record<string, string> = {
'yourbrand-denver.com': '/locations/denver-co',
'yourbrand-austin.com': '/locations/austin-tx',
// ... 在生产中从边缘配置加载
};
export function middleware(request: Request) {
const hostname = new URL(request.url).hostname;
const rewritePath = DOMAIN_MAP[hostname];
if (rewritePath) {
return NextResponse.rewrite(new URL(rewritePath, request.url));
}
return NextResponse.next();
}
没有插件。没有 sunrise.php dropin。没有脆弱的映射表。只是边缘处的重写规则。
迁移路径:从 WordPress Multisite 迁出
如果你目前在拥有 20+ 个位置的 WordPress Multisite 上,这是现实的迁移路径:
第 1 阶段:数据导出(1-2 周)
使用 WordPress REST API 或 WP-CLI 从每个子站点中提取内容。不要尝试手动重新映射前缀表。使用 API——它抽象了前缀噩梦。
# 从子站点 23 导出所有文章
wp post list --url=example.com/location-23 --format=json > location-23-posts.json
# 导出所有媒体
wp media list --url=example.com/location-23 --format=json > location-23-media.json
第 2 阶段:模式设计(1 周)
围绕你的实际内容模型(而不是 WordPress 的文章/postmeta 模型)设计你的 Supabase 模式。这是你修复多年来积累的数据模型技术债的时刻。
第 3 阶段:内容迁移(1-2 周)
编写将 WordPress 内容转换为新模式的迁移脚本。处理序列化数据、短代码和 Gutenberg 块。
第 4 阶段:前端构建(3-4 周)
构建 Next.js 前端。这是你看到实际性能收益的地方。在 WordPress 上花费 1.5 秒的页面现在在 200ms 以下加载。
第 5 阶段:DNS 转换(1 天)
将你的域指向新基础设施。保持旧网络在 30 天内作为只读备份运行。
对于需要此迁移流程帮助的业务,我们已经在我们的 无头 CMS 开发页面记录了我们的方法。我们已经做了足够多的这类迁移,以至于知道尸体埋在哪里。
真实数字:性能和成本比较
这是我们在 2025 年第一季度完成的实际迁移中的数据——一个拥有 34 个位置的牙科诊疗网络:
| 指标 | WordPress Multisite(之前) | Next.js + Supabase(之后) |
|---|---|---|
| 平均 TTFB | 1,240ms | 89ms |
| 最大内容绘制 | 3.8s | 1.1s |
| 每月托管成本 | $347/月(WP Engine) | $45/月(Vercel Pro + Supabase Pro) |
| 添加新位置的时间 | 2-3 小时(手动设置) | 15 分钟(插入行 + 内容) |
| 更新所有位置的时间 | 插件更新 + 祈祷 | git push |
| Core Web Vitals 通过率 | 34 个位置中的 12 个 | 34 个位置中的 34 个 |
| 安全事件(12 个月) | 3 | 0 |
仅托管成本的降低在 8 个月内就为迁移付出了代价。性能改进在 90 天内为 28 个位置中的 28 个驱动了本地搜索排名的可衡量增长。
如果你正在评估自己网络的成本,我们的 定价页面为多位置项目提供了透明的成本分解。
常见问题解答
WordPress Multisite 对于管理多个位置来说是个好选择吗? 对于少数位置(少于 10 个)具有集中式管理的情况,WordPress Multisite 可以工作。但它并未设计用于需要独立操作、数据隔离或高规模性能的多位置业务。共享数据库架构创建了随着每个位置的添加而加剧的安全、性能和操作问题。
WordPress Multisite 的最大问题是什么? 七个主要问题是:共享漏洞表面(一个漏洞击中所有站点)、插件冲突倍增(一个冲突导致每个站点停机)、非线性性能下降、噩梦般的迁移/提取过程、零数据库级数据隔离、超级管理员瓶颈(所有管理更改)和脆弱的域映射。这些问题是架构性的——它们不能通过插件或更好的托管来修复。
WordPress Multisite 能处理 100+ 个站点吗? 从技术上讲,可以。从实际来说,超过 30-50 个站点会变得非常痛苦。在 100 个站点处,你正在处理单个 MySQL 实例中 1,100+ 个数据库表、严重的查询规划器降级以及需要专门 DevOps 员工的操作复杂性。在 500 个站点处,对于大多数托管环境来说,这是不可行的。
WordPress Multisite 的最佳替代方案是什么用于多个位置? 一个使用 Next.js 或 Astro 前端和 PostgreSQL 数据库(如 Supabase)的无头架构,使用行级安全提供真正的数据隔离,性能显著提高,托管成本更低,位置管理平凡。每个位置是一个数据库行,而不是整个 WordPress 安装。
你如何从 WordPress Multisite 迁移到无头架构? 最安全的方法是通过 WordPress REST API 或 WP-CLI 而不是尝试手动重新映射前缀数据库表来提取内容。每个子站点导出内容,在目标数据库中设计干净的模式,编写转换脚本,构建新的前端,并切换 DNS。对于拥有 20+ 个站点的网络,计划总共 6-10 周。
WordPress Multisite 会影响 SEO 吗? 间接地,是的。WordPress Multisite 的性能下降导致页面加载缓慢,这会伤害 Core Web Vitals 评分。谷歌已确认页面体验信号影响排名。在我们的迁移数据中,82% 的位置在迁移到具有 200ms 以下 TTFB 的无头架构后,在 90 天内看到了改进的本地搜索排名。
WordPress Multisite 对于业务数据安全吗? 不,如果你将安全性定义为包括位置之间的数据隔离。WordPress Multisite 使用一个数据库和一组凭证。任何子站点上的 SQL 注入都可以访问每个其他子站点的数据。对于受 HIPAA、SOC 2、PCI DSS 或类似合规要求的业务,缺乏数据库级隔离是一个重大责任。
运行 WordPress Multisite 与无头替代方案的成本是多少? 用于 Multisite 的托管 WordPress 托管通常运行 $200-800/月,取决于站点数量和流量水平(WP Engine 的 Multisite 计划在 2025 年以其增长层级 $240/月开始)。Vercel Pro($20/月)加上 Supabase Pro($25/月)的可比无头设置可以处理相同的流量,成本仅为其一小部分。无头方法的初始构建投资更高,但每月运营成本显著降低。如果你想要针对你网络规模的具体成本比较,欢迎联系我们。