核心问题是租户隔离设计不合理:tenant_id必须建索引且为联合索引最左前缀,查询须显式带上tenant_id并确保执行计划为ref/eq_ref;禁用跨租户宽表,推荐分表/分库或tenant_id+时间分区;统计应预计算并按tenant_id缓存隔离;需权限与执行计划双重校验。

多租户SQL报表统计变慢,核心问题往往不在SQL本身,而在于租户隔离设计是否合理——尤其是数据层隔离方式、索引覆盖、查询路由机制是否与租户维度对齐。
租户字段必须有索引且参与查询条件
常见误区是只在业务表加tenant_id字段,却不建索引,或查询时未显式带上tenant_id。数据库无法有效剪枝,导致全表扫描。
- 所有含tenant_id的表,tenant_id必须是联合索引的最左前缀(如(tenant_id, create_time)),尤其用于时间范围+租户的报表场景
- 报表SQL禁止出现WHERE 1=1后动态拼接tenant_id;必须确保执行计划中type=ref/eq_ref,而非ALL
- 使用EXPLAIN验证:重点看key列是否命中索引,rows是否接近租户内实际数据量级
避免跨租户视图或宽表聚合
为“统一报表”建一张包含所有租户数据的大宽表或视图,看似简洁,实则破坏隔离边界,使每次查询都加载无关租户数据。
- 按租户分表(如order_tenant_001)或分库,配合中间件(如ShardingSphere)自动路由,物理隔离最彻底
- 若用共享表模式,报表查询必须强制走tenant_id + 时间分区(如按月分表+tenant_id索引),禁用无tenant_id的COUNT(*)或SUM(*)全量聚合
- 缓存层也需按tenant_id+维度键隔离(如redis key设为report:summary:{tenant_id}:{yyyymm}),避免缓存污染
统计逻辑下沉到租户粒度预计算
实时JOIN多表+GROUP BY租户做汇总,IO和CPU开销随租户数线性增长。应把计算压力从查询时移到写入或定时任务时。
- 在订单、支付等关键事件发生时,同步更新该租户的轻量统计表(如tenant_daily_stats),字段仅保留报表必需指标
- 对时效性要求不高的报表(如昨日汇总),用凌晨定时任务刷新,查表直接SELECT,不碰原始明细
- 租户间统计互不影响,既提速又防拖垮——一个大租户的数据激增,不会波及其他租户查询响应
权限与执行计划双重校验
应用层传tenant_id后,数据库侧仍需防御性拦截,防止越权或漏传导致全量扫描。
- 在数据库连接层(如ProxySQL)或ORM拦截器中,校验SQL是否含tenant_id条件,缺失则拒绝执行
- 使用行级安全策略(PostgreSQL Row Level Security)或MySQL 8.0的CHECK OPTION视图,从内核层强制tenant_id过滤
- 定期审计慢日志,筛选出tenant_id IS NULL或tenant_id = ?但参数为空的SQL,快速定位代码漏洞










