滚动新闻 →
搜集海底电缆情报?中共调查船异常绕台 Google Cloud 服务区域如何选择,全球业务部署时应该考虑哪些因素 英国男子历时28年徒步穿越四大洲 临近终点 SaaS 多租户表的索引设计,tenant_id 应该放在什么位置 波兰欧洲议员:共产主义意味着饥饿、死亡和奴役 佛罗里达棕榈滩商务机坠毁致9人遇难 空管记录揭示惊险瞬间 移动硬盘插上以后指示灯亮但电脑不识别,应该检查哪些硬件 iPhone Duo国行版遭质疑偷工减料 网友:实为功能阉割 Windows 11 家庭安全设置怎么用?儿童设备管理需要掌握哪些功能 欧洲青年示威为何走向暴力 极左翼势力渗透与共产主义政治影响值得警惕 波兰宪法法院危机背后的美欧政治角力 左右派争夺国家未来 波兰宪法法院风波敲响警钟 左翼政府与国家主权的正面冲突 中国商务部拟出台商业秘密保护新规 招商与管制政策面临挑战 韦伯望远镜揭示N159恒星诞生区的剧烈演化过程 MySQL 联合索引怎么设计?多条件查询中的索引顺序非常重要 万圣节期间多地执法引争议 上海市民围警车抗议 美日印澳构建非中国供应链体系 针对性应对关键矿产限制 傅成玉退休十一年后被查 石油系统旧账与中共高层权力博弈再受关注 从古诗词看古人的离别文化 波兰司法危机背后的政治路线之争 纳沃罗茨基与图斯克正面交锋 反诈博主徽剑遭雇凶杀害 主犯获判死缓背后仍有多重疑团 法国岩洞壁画3万年保存完好 或因方解石层保护 2026年11月维权事件追踪:退休金被停发军嫂就医困境 美伊军事行动未定,卢比奥称谈判仍在进行中 传统中医与中国茶文化的关系 欧洲多国示威升级为暴力冲突,极端分子渗透引关注 欧洲多国博物馆频遭盗窃 文物安全引担忧 万圣节期间中国多地出现执法行动 海外华人抗议活动引关注 保加利亚总统选举的两大政治阵营 国家主权派与亲欧盟自由派正面交锋 前美军顾问警告:中共黑客威胁升级至军事层面 古代文人为什么重视书房环境 胡歌自曝好友因心梗离世 现随身携带救心丸 FBI指控朝鲜参与63亿美元加密货币盗窃案 如何让孩子真正理解道歉的意义 土耳其研究揭示塑胶茶包热水冲泡释放大量微塑胶颗粒 新手剪视频应该从哪里开始 美驻俄大使馆预警莫斯科恐遭恐袭 俄景点紧急关闭 鸦片战争为什么会爆发 中共中央强化党内民主规范 引发对权力集中模式的讨论 网购侏儒猪失控长至200斤 女子带其散步仅限无人区

SaaS 多租户表的索引设计,tenant_id 应该放在什么位置

发布时间: 2026-11-01 16:00:02    最后更新: 2026-11-01 17:24:25    阅读:8  约10 分钟阅读     

SaaS 多租户表的索引设计,tenant_id 应该放在什么位置
图片说明:示意图   图片来源:Public Domain(公有领域)
在SaaS系统中,tenant_id 几乎是最常见的字段之一。订单、用户、合同、工单、账单等业务数据,往往都放在同一张表里,通过 tenant_id 区分不同客户。于是很多开发者会形成一个简单规则:只要是多租户表,所有联合索引都把 tenant_id 放在第一位。

这个做法有时正确,但绝不是数据库设计中的通用定律。联合索引的字段顺序,最终应该由真实查询决定,而不是由“这是租户字段”决定。tenant_id 放在第一位、第二位甚至完全不进入某个索引,都可能是合理设计。

最常见的情况,是业务查询几乎都带有租户条件。例如订单列表经常执行:

SELECT order_id, status, create_time
FROM orders
WHERE tenant_id = 'T123'
ORDER BY create_time DESC
LIMIT 20;

这种情况下,(tenant_id, create_time) 通常是非常自然的索引设计。MySQL可以先定位到某个租户的数据范围,再按照 create_time 的索引顺序取得结果,避免为了完成排序而扫描大量不相关记录。

如果查询变成:

SELECT order_id
FROM orders
WHERE tenant_id = 'T123'
AND status = 'paid'
ORDER BY create_time DESC
LIMIT 20;

则可以考虑 (tenant_id, status, create_time)。但这里不能简单理解成“字段越多越快”。索引越宽,写入时维护的成本越高,同时还会占用更多Buffer Pool空间。最终是否值得建立,应该结合实际查询频率、数据分布和执行计划判断。

tenant_id 是否应该放在第一位,关键要看它前面的字段有没有实际意义。例如某个系统经常执行:

SELECT *
FROM orders
WHERE status = 'pending'
AND tenant_id = 'T123'
ORDER BY create_time DESC;

如果系统还有大量不限定租户、而是按照 status 进行后台任务扫描的查询,那么 (status, tenant_id, create_time) 也可能具有实际价值。SQL语句中 WHERE 条件的书写顺序,并不能决定联合索引的字段顺序。真正需要考虑的是等值过滤、范围条件、排序、分页以及不同查询之间的共同访问路径。

对于典型的租户列表查询,(tenant_id, create_time) 往往比单独建立 tenant_id 索引更有价值。原因很简单:如果查询既需要限定租户,又需要按照时间排序,那么单列 tenant_id 索引只能帮助定位租户数据,排序仍可能需要额外工作;联合索引则有机会同时承担过滤和排序。

不过,不能因此认为单独的 tenant_id 索引没有意义。如果系统存在大量只按照租户统计、查询或者连接其他条件的操作,它仍然可能成为有效的访问路径。更重要的是,不要用一个固定的“选择性阈值”判断 tenant_id 索引有没有价值。

例如:

SELECT COUNT(DISTINCT tenant_id) / COUNT(*)
FROM orders;

这个结果只是描述不同租户值在整张表中的分布情况,并不能直接决定索引是否有效。假设一张表有1000万条记录、1000个租户,平均每个租户有1万条记录,那么 tenant_id 的全局选择性并不高,但对于查询某一个租户来说,索引仍然可能大幅减少需要扫描的数据量。更何况SaaS系统经常存在“大小租户”差异,一个超级租户可能占据整张表很大比例的数据,这时候平均值更不能代表实际查询成本。

这也是SaaS索引设计中非常容易被忽略的问题:租户数据通常并不均匀。一个小租户可能只有几千条记录,而一个大型企业客户可能拥有几百万甚至更多记录。如果所有查询都使用完全相同的索引策略,超级租户的查询行为可能与普通租户完全不同。此时需要通过真实执行计划、慢查询日志以及生产环境的查询统计来判断,而不是套用一个固定公式。

分页场景也需要特别注意。传统的:

WHERE tenant_id = 'T123'
ORDER BY create_time DESC
LIMIT 10 OFFSET 10000

虽然可以使用 (tenant_id, create_time),但随着 OFFSET 增大,数据库仍然需要跳过大量记录。对于大数据量SaaS系统,可以进一步考虑基于唯一排序键的Keyset Pagination,例如记录上一页最后一个 create_time 和 order_id,下一页直接从这个位置继续读取。对应索引通常可以设计为 (tenant_id, create_time, order_id),具体顺序则需要结合排序方向和唯一性要求确定。

另一个常见误区,是认为只要索引中包含了 tenant_id 和其他过滤字段,就一定属于覆盖索引。实际上,覆盖索引的定义取决于查询需要返回的列。如果查询:

SELECT order_id, status, create_time
FROM orders
WHERE tenant_id = 'T123'
AND status = 'paid';

而索引只包含 (tenant_id, status),那么它能够很好地帮助过滤,但因为还需要读取 order_id、create_time 等列,是否需要回表取决于具体表结构和查询计划。只有索引本身包含查询所需要的全部列时,才真正构成覆盖索引。

至于把 tenant_id 放进主键,更不能简单理解为“实现租户隔离”。如果使用:

PRIMARY KEY (tenant_id, order_id)

它确实可以改变聚簇索引的组织方式,并且在某些租户范围查询和分片设计中非常有用。但它也会影响InnoDB主键大小、二级索引存储以及其他查询路径。如果 order_id 本身已经是全局唯一ID,强行修改主键结构未必能够获得实际收益。

更重要的是,数据库索引从来不是SaaS租户安全边界。即使建立了 (tenant_id, order_id),如果应用程序执行查询时忘记加入 tenant_id 条件,索引也不会阻止一个租户读取另一个租户的数据。真正的数据隔离应该由应用层授权、数据访问层约束以及必要的数据库安全机制共同保证。

因此,在SaaS系统中,tenant_id 最值得考虑的并不是“必须放第一位”,而是“哪些查询路径必须经过它”。可以先把生产环境中的主要查询整理成查询矩阵,例如租户订单列表、租户订单详情、状态筛选、时间范围查询、后台任务扫描、统计报表和超级管理员查询,然后分别观察过滤条件、排序方式、返回字段和访问频率,再设计联合索引。

同时要使用 EXPLAIN 和 EXPLAIN ANALYZE 验证实际执行计划。重点观察访问类型、实际扫描行数、估算行数、排序、临时表以及执行时间。对于生产系统,还可以结合慢查询日志、Performance Schema和APM数据判断某个索引到底有没有真正解决问题。

还有一点经常被忽略:tenant_id 本身通常是一个低基数业务字段,但它在联合索引中的价值并不等于它单独作为索引的价值。(tenant_id, create_time)、(tenant_id, status, create_time)、(status, tenant_id, create_time)解决的是不同的查询问题。不能因为三个索引都包含 tenant_id,就认为它们互相重复,也不能因为某个字段选择性低,就直接认定它没有索引价值。

当SaaS数据从几十万增长到几千万甚至更高时,索引设计的重要性会进一步提高,但索引也不是无限扩展数据库容量的办法。随着数据持续增长,还需要考虑历史数据归档、分区、读写分离、缓存、异步处理以及最终可能出现的分库分表。尤其是超级租户,如果单个租户已经占据大量数据,单纯增加 tenant_id 索引并不能解决所有问题。

所以,SaaS多租户表设计可以记住一个更实用的原则:tenant_id 经常是联合索引的重要组成部分,但它并没有天然的“第一位特权”。如果查询主要是“某租户的数据,再按时间排序”,tenant_id 往往适合放在前面;如果查询模式首先按照其他字段筛选,或者存在大量跨租户后台任务,则其他字段也可能应该排在前面。最终决定索引顺序的不是字段名称,而是真实的查询路径、数据分布和执行计划。

好的SaaS索引设计,本质上不是给每张表套一个 tenant_id 模板,而是让数据库能够用最少的扫描工作找到真正需要的数据,同时把写入成本、存储成本和未来的数据增长一起考虑进去。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP