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 不是真正的多站点:可扩展到 500 个位置的架构

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_blogswp_sitewp_sitemetawp_registration_logwp_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_postswp_12_optionswp_12_postmeta 运行自己的一组查询。每个其他子站点也是如此,所有这些都在同一 MySQL 实例上。

MySQL 的查询规划器变得困惑。表缓存填满。每个前缀表都维护自己的索引,但缓冲池是共享的。随着你添加子站点,性能非线性下降。从 10 到 20 个子站点不是两倍的负载——取决于流量模式,可能是三倍或四倍的负载,因为锁争用和缓冲池颠簸。

我曾对一个 40 子站点网络进行基准测试。子站点 #1 上的平均查询时间是 45ms。当我们测试子站点 #38 时,平均查询时间攀升到 380ms。相同的查询结构。每个站点相同的数据量。数据库只是淹没在表中。

4. 迁移是一场噩梦

尝试将子站点 #23 从 50 站点网络提取到其自己的独立 WordPress 安装。这是你需要做的事:

  1. 导出所有 wp_23_ 前缀的表
  2. 将每个表从 wp_23_ 前缀重新映射到 wp_ 前缀
  3. 重新序列化所有选项和小部件数据(WordPress 在数据库中存储序列化的 PHP,当你改变前缀时字符串长度会改变)
  4. 将媒体路径从 /uploads/sites/23/ 重新映射到 /uploads/
  5. 搜索和替换所有内部 URL
  6. wp_usermeta 中重新映射用户功能从 wp_23_capabilitieswp_capabilities
  7. 从共享的 wp_users 表中提取用户(仅属于子站点 #23 的用户)
  8. 重新创建用户到站点的关系

序列化中的一个错误,你得到损坏的数据。小部件设置消失。主题定制器选项破坏。菜单结构消失。我做过几十次这个提取过程,即使使用像 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.comyourbrand-denver.com?WordPress Multisite 不以可靠的方式本地处理这个问题。你需要一个域映射解决方案,而内置的 sunrise.php dropin 方法是臭名昭著的不可靠。

为映射的域的 SSL 证书添加了另一层。DNS 配置添加另一个。每个映射的域都是你的超级管理员必须管理的另一个潜在故障点。一个 DNS 更改、一个过期的证书、一个错误配置的映射条目,位置的站点就会下线。

WordPress Multisite 何时可行(坦诚相待)

我不会假装它没有用。WordPress Multisite 在特定场景中运行良好:

  • 小型网络(少于 10 个站点),其中所有站点由同一团队管理
  • 大学部门站点,其中集中式控制实际上是期望的
  • 开发/暂存/生产镜像用于同一项目
  • 博客网络,其中内容隔离不是关键

如果你有 5-8 个站点、一个有能力的系统管理员,并且你不需要站点之间的数据隔离——Multisite 很好。问题从你尝试将其扩展到几十或数百个位置时开始。

WordPress Multisite 不是真正的多站点:可扩展到 500 个位置的架构 - 架构

实际可扩展到 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/月)的可比无头设置可以处理相同的流量,成本仅为其一小部分。无头方法的初始构建投资更高,但每月运营成本显著降低。如果你想要针对你网络规模的具体成本比较,欢迎联系我们