当前位置:首页 > 文章列表 > 数据库 > MySQL > MySQL 多租户订单表架构演进:从 tenant_id 联合索引到租户分片

MySQL 多租户订单表架构演进:从 tenant_id 联合索引到租户分片

来源:17golang原创 2026-07-02 13:34:56 0浏览 收藏

多租户系统里的订单表,早期通常会把所有租户的数据放在一张 orders 表里,再用 tenant_id 区分归属。数据量小的时候这很清爽;一旦某个大客户的订单量、查询量和导出任务明显高于其他租户,单表联合索引只能解决一部分查询成本,真正的架构问题会变成:哪些租户继续共享,哪些租户需要被路由到独立资源里。

核心要点
  • 多租户订单表的第一条规则,是所有核心查询都必须带上 tenant_id,并让联合索引从租户维度开始。
  • 联合索引能减少扫描范围,但不能隔离一个热点租户对 CPU、IO、连接池和慢查询队列的影响。
  • 当大租户长期拉高 rows、慢日志和接口延迟,就要把“加索引”升级为“租户路由 + 数据迁移 + 回读校验”。
  • 分区表可以帮助部分查询裁剪无关分区,但它不是租户级资源隔离方案,主键和唯一键限制也要提前核对。
目录
  • 规模背景:一张 orders 表承载所有租户
  • 原架构瓶颈:一个热点租户拖慢整张订单表
  • 第一阶段:用 tenant_id 领头的联合索引稳住主查询
  • 第二阶段:拆出热点租户的路由和写入链路
  • 关键取舍:分区、分表和独立库分别解决什么问题
  • 上线后看哪些信号
  • 相关问题
  • 总结

规模背景:一张 orders 表承载所有租户

先看一个常见表结构。订单表既要给后台列表查,又要给对账、导出、售后、统计任务用。早期为了开发简单,所有租户共享一张表:

CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  tenant_id BIGINT NOT NULL,
  user_id BIGINT NOT NULL,
  status TINYINT NOT NULL,
  amount DECIMAL(12, 2) NOT NULL,
  created_at DATETIME NOT NULL,
  updated_at DATETIME NOT NULL
);

列表查询一般长这样:

SELECT id, status, amount, created_at
FROM orders
WHERE tenant_id = ?
  AND status = ?
  AND created_at >= ?
ORDER BY created_at DESC
LIMIT 50;

这个模型的优点很明显:表少、代码简单、统计也容易写。问题也同样明显:所有租户共用同一张物理表、同一组索引、同一个实例资源。只要一个租户的数据量明显偏大,或者某个租户开启高频导出,其他租户的正常查询也可能被拖住。

原架构瓶颈:一个热点租户拖慢整张订单表

MySQL 官方文档说明,索引用于快速找到具有特定列值的行;多列索引可以服务测试索引中全部列或最左前缀列的查询。这给了我们第一层优化方向:把高频条件放进合适的联合索引里。但多租户场景的麻烦在于,热点租户并不只是“查询没走索引”,它常常是“走了索引也要读很多行”。

MySQL 多租户 orders 表中 tenant_id 查询命中索引但热点租户 rows high 后需要 split tenant 的决策路径图

假设普通租户每月只有几千单,热点租户每月有几百万单。相同的 SQL、相同的索引,在普通租户上可能只扫几十行,在热点租户上却要扫大量历史记录。此时单表继续扩容会遇到几个瓶颈:

  • 索引页更大,缓存命中率下降,热点租户把更多 buffer pool 空间占走。
  • 大范围查询和导出任务增加磁盘读写压力,影响普通租户列表页。
  • 慢查询排队会占用连接池,让应用侧看起来像“所有租户都慢”。
  • 归档、修复、回放这类后台任务越来越难按租户隔离。

第一阶段:用 tenant_id 领头的联合索引稳住主查询

在没有分片之前,先把主查询的索引设计做好。对上面的订单列表,可以先建一个符合过滤和排序方向的联合索引:

CREATE INDEX idx_orders_tenant_status_created
ON orders (tenant_id, status, created_at DESC);

这样做的核心不是“字段越多越好”,而是把租户边界放在最前面。多列索引有最左前缀规则,tenant_id 作为第一列,可以让同一租户内的状态和时间范围查找更集中。验证时看三类信号:

检查项 希望看到的变化 说明
EXPLAINkey 使用 idx_orders_tenant_status_created 说明优化器选择了目标索引
rows 从大范围下降到租户内较小范围 说明扫描范围被租户和条件收窄
慢日志 普通租户查询明显减少 说明主链路先被稳住

如果列表还需要按用户查,可以再根据真实查询频率补充 (tenant_id, user_id, created_at)。不要给每个接口都加一条索引,索引越多,写入、更新和空间成本也越高。

第二阶段:拆出热点租户的路由和写入链路

当联合索引已经命中,但热点租户仍然长期拉高延迟,就要从表设计进入路由设计。比较稳的做法不是一次性全量拆所有租户,而是先给热点租户建立路由表:

CREATE TABLE tenant_db_route (
  tenant_id BIGINT PRIMARY KEY,
  route_type VARCHAR(20) NOT NULL,
  shard_key VARCHAR(64) NOT NULL,
  updated_at DATETIME NOT NULL
);

应用写入订单前先查本地缓存的租户路由:普通租户继续写共享库,热点租户写独立分片。读接口也走同一套路由,避免写到新分片、读还去旧表的割裂问题。

MySQL 多租户系统通过 route table 把普通租户写入 orders_01、热点租户写入 tenant shard 和 orders_02 的分片流程图

迁移时建议按下面顺序推进:

  1. 先建新分片和目标表,表结构、索引、字符集和时区规则保持一致。
  2. 按租户维度复制历史数据,复制后比对订单数、金额合计和最大 id
  3. 应用侧开启双读校验,只对目标租户生效,发现差异可以退回共享表读取。
  4. 切写入路由,让热点租户的新订单进入新分片。
  5. 观察一段时间后,再清理旧表中已经迁出的热点租户数据。

这一步的关键是“按租户渐进迁移”。如果一开始就分所有租户,很容易把路由、迁移、回滚和报表链路一起复杂化。

关键取舍:分区、分表和独立库分别解决什么问题

多租户订单表变慢时,团队常会在分区、分表、分库之间摇摆。它们解决的问题不同,不能只看名字相似。

方案 主要收益 适合场景 需要注意
联合索引 减少单次查询扫描范围 大多数租户查询还在可控范围内 无法隔离热点租户资源消耗
MySQL 分区 在条件可裁剪时减少无关分区扫描 按时间或固定键管理历史数据 主键、唯一键和分区表达式有限制,不能当成完整分片
租户分表 降低单表数据量,迁移边界清晰 热点租户少、表结构稳定 报表和跨租户查询要额外聚合
独立库或独立实例 隔离连接、IO、缓存和维护窗口 大客户、强隔离、付费等级差异明显 运维成本、路由和备份策略都会变复杂

MySQL 分区裁剪的思路是:当条件能明确落到某些分区时,就不扫描不可能命中的分区。这个能力很适合按时间清理和部分范围查询,但它仍在同一个表模型里工作。官方文档也明确提到分区键与主键、唯一键之间有约束关系,做方案前要先核对现有唯一约束是否允许这样改。

上线后看哪些信号

拆分不是把数据搬走就结束。上线后至少要观察三个层面的信号:

  • 查询层:热点租户迁出后,共享表主查询的 rows、慢日志次数、接口 P95 是否下降。
  • 写入层:热点租户新订单是否全部进入新分片,路由缓存是否有过期和误命中。
  • 运维层:备份、归档、数据修复、账单统计是否已经适配新的路由关系。

更稳的验收方式,是把迁移前后的核心 SQL 都留一份样例:

EXPLAIN
SELECT id, status, amount, created_at
FROM orders
WHERE tenant_id = 8421
  AND status = 2
  AND created_at >= '2026-07-01'
ORDER BY created_at DESC
LIMIT 50;

如果热点租户被迁出,共享表上这类查询应该不再拖累其他租户;新分片上的查询则要单独看索引、归档和限流策略。分片后的性能治理不是结束,而是把“全局混在一起慢”改成“按租户定位和治理”。

相关问题

多租户表一定要用 tenant_id 做联合索引第一列吗?

大多数按租户隔离的业务查询都应该这样做。只要接口天然属于某个租户,tenant_id 放在联合索引前面可以先收窄租户范围,再按状态、时间或用户继续过滤。

热点租户出现后,应该先分表还是先独立库?

先看瓶颈在哪里。如果只是单表过大,分表可能够用;如果连接、缓存、IO 和维护窗口都被大租户占用,独立库或独立实例更符合隔离目标。

MySQL 分区能不能替代租户分片?

通常不能。分区可以帮助管理和裁剪部分查询范围,但它不等于资源隔离,也不能替代应用层路由。多租户隔离通常还要考虑连接池、备份、权限、账单和运维边界。

租户迁移时最怕什么问题?

最怕写入和读取路由不一致。建议先做历史数据校验,再做双读或抽样回读,最后切写入路由,并保留可退回共享表的开关。

总结

MySQL 多租户订单表的演进,不是一上来就分库分表。更可靠的路线是:先保证所有主查询带 tenant_id,用租户维度领头的联合索引压低普通查询成本;当热点租户继续制造高扫描、高延迟和队列压力,再通过租户路由把它迁到独立分片。这样既保留早期单表的简单性,也给大客户和高峰流量留出清晰的扩展路径。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Linux rsync 同步目录如何排除文件并保留权限?安全命令配方Linux rsync 同步目录如何排除文件并保留权限?安全命令配方
上一篇
Linux rsync 同步目录如何排除文件并保留权限?安全命令配方
Go 服务的 pprof 能直接暴露公网吗?排障入口上线前的安全判断
下一篇
Go 服务的 pprof 能直接暴露公网吗?排障入口上线前的安全判断
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    71次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    233次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    155次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    87次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    64次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码