网易首页 > 网易号 > 正文 申请入驻

SaaS多租户数据隔离:一个WHERE子句的生死线

0
分享至

你的SaaS跑了三个月,客户开始要仪表盘。第一个需求很简单——Acme Corp想看到自己的订单、注册量和收入。你随手写了个WHERE tenant_id = 42,上线收工。

六个月后,400个租户、30套报表、一个能实时写SQL的AI助手,再加上某个周二早上忘了加WHERE子句的工程师。现在Acme Corp能看到Globex的数据了。这种周一早晨的头条新闻,你不想上。


这篇文章聊的是多租户SQL报表的架构设计:怎么让一次疏忽永远成不了数据泄露。我们会覆盖租户Schema设计、Postgres行级安全(RLS)、JWT对接、性能索引,以及生产环境里那些咬人的坑。


三种多租户模式(为什么有一种在报表场景胜出)

动笔写SQL之前,先选隔离模型。经典选项有三个:

模式实现方式适用场景独立数据库每个租户一个Postgres实例企业级隔离,成本极高独立Schema共享实例,租户各有一个Schema中等规模,Schema管理复杂共享Schema + tenant_id单表加租户标识列90%的SaaS首选

对90%的SaaS应用来说,共享Schema加tenant_id列是正确的选择。便宜、能扩展,Postgres还给了足够的安全工具。下面的内容都基于这个模式。

第一步:每行数据打上tenant_id标签

所有存客户数据的表都需要租户标识。没有例外。包括eventsaudit_logsai_query_history这些边缘表。

CREATE TABLE orders (id BIGSERIAL PRIMARY KEY,tenant_id BIGINT NOT NULL REFERENCES tenants(id),customer_id BIGINT NOT NULL,amount_cents INT NOT NULL,status TEXT NOT NULL,created_at TIMESTAMPTZ DEFAULT now()CREATE INDEX orders_tenant_created_idxON orders (tenant_id, created_at DESC);

两个细节:tenant_idNOT NULL(空值意味着孤儿行,会绕过你的过滤器);索引把tenant_id放最前面。每个报表查询都会按租户过滤,这就是你的核心索引。

第二步:用行级安全(RLS)做最后一道防线

应用层过滤够好,直到有人忘记。Postgres的行级安全在数据库层强制执行过滤,就算裸跑SELECT * FROM orders,返回的也只是当前租户有权查看的行。

-- 1. 开启RLSALTER TABLE orders ENABLE ROW LEVEL SECURITY;-- 2. 定义策略CREATE POLICY tenant_isolation ON ordersFOR ALLUSING (tenant_id = current_setting('app.tenant_id')::BIGINT)WITH CHECK (tenant_id = current_setting('app.tenant_id')::BIGINT);

USING子句控制你能读哪些行;WITH CHECK子句防止你往其他租户插入或更新数据。两头都堵死。

现在每个连接必须在查询前设置app.tenant_id

SET LOCAL app.tenant_id = '42';SELECT count(*) FROM orders; -- 自动限定在租户42

SET LOCAL把设置绑定到当前事务,不会泄漏到连接池里的其他请求——这一点很重要。

第三步:把租户上下文对接认证层

这个设置必须来自可信来源。标准做法是从验签过的JWT里提取:

# 伪代码——每个请求在事务开始时执行def set_tenant_context(conn, jwt_token):claims = verify_jwt(jwt_token)  # 验签tenant_id = claims['tenant_id']  # 从payload提取conn.execute("SET LOCAL app.tenant_id = %s", tenant_id)return conn

关键链条:JWT签名验证 → 提取租户ID → 设置会话变量 → 所有后续查询自动隔离。中间任何一环断了,查询就会因为缺少app.tenant_id而失败,而不是错误地返回数据。


第四步:索引策略别偷懒

RLS有性能成本。Postgres会对每行检查策略条件,糟糕的索引会让报表查询变成全表扫描。

核心原则:

  • 所有RLS过滤列都要有索引(这里是tenant_id
  • 复合索引把租户列放最左(tenant_id, created_at而不是反过来)
  • 覆盖索引减少回表(INCLUDE常用查询字段)

一个典型报表查询:SELECT * FROM orders WHERE tenant_id = 42 AND created_at > '2024-01-01'。如果索引是(created_at, tenant_id),Postgres可能先扫大量历史数据再过滤租户;顺序反过来,直接定位到租户的时间切片。

生产环境的三个咬人坑

坑一:连接池的变量泄漏

用PgBouncer或类似池子时,SET不加LOCAL会让租户ID残留在连接里。下一个请求复用这个连接,就串了数据。必须用SET LOCAL,且确保事务结束时清理。

坑二:超级用户绕过RLS

Postgres的BYPASSRLS属性让超级用户无视策略。你的应用连接应该用普通角色,管理操作走单独的审计通道。别让postgres用户跑应用代码。

坑三:AI生成SQL的不可控

那个"实时写SQL的AI助手"是最大的变量。LLM可能生成子查询、CTE、或者跨表关联,RLS策略必须覆盖所有路径。建议:给AI设只读角色,启用RLS,并且在应用层再加一道查询审计——拦截包含tenant_id IS NULL或明显跨租户模式的SQL。

最后的检查清单

  • 所有客户数据表都有NOT NULLtenant_id
  • RLS开启,策略覆盖USINGWITH CHECK
  • 租户ID从JWT提取,验签后才进数据库
  • 索引最左列是tenant_id
  • 连接池配置正确,SET LOCAL绑定事务
  • AI生成的SQL有额外的审计层

数据隔离不是功能,是底线。一个WHERE子句的遗忘成本,可能是整个公司的信任破产。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
新冠后遗症对人体的最大影响,很多人深受其害,有些人还不自知

新冠后遗症对人体的最大影响,很多人深受其害,有些人还不自知

呼吸科大夫胡洋
2026-02-22 11:39:12
64岁陈秀雯近况曝光,“消失”6年变了模样,那么美却被婚姻毁了

64岁陈秀雯近况曝光,“消失”6年变了模样,那么美却被婚姻毁了

文刀贰
2026-05-13 23:25:20
云南腾冲枪击案:村中首富持冲锋枪疯狂杀人,除夕夜两家被灭门

云南腾冲枪击案:村中首富持冲锋枪疯狂杀人,除夕夜两家被灭门

飞云如水
2024-07-04 22:24:01
A股:突然跳水大跌,原因是什么?释放什么信号?行情到顶了吗?

A股:突然跳水大跌,原因是什么?释放什么信号?行情到顶了吗?

虎哥闲聊
2026-05-14 11:37:25
外交防线全面失守!特朗普无视日本直飞北京,高市拥核梦要碎了?

外交防线全面失守!特朗普无视日本直飞北京,高市拥核梦要碎了?

别人都叫我阿螫
2026-05-14 11:48:22
张雪召回问题车辆 网友质疑:当初怎么说雷军的?应该退车退款

张雪召回问题车辆 网友质疑:当初怎么说雷军的?应该退车退款

念洲
2026-05-13 09:49:13
结婚才2年,萧敬腾就尝到老妻少夫的残酷,其实舒淇早就把话说透

结婚才2年,萧敬腾就尝到老妻少夫的残酷,其实舒淇早就把话说透

一盅情怀
2026-05-11 11:23:33
唐嫣《爱情没有神话》37集大结局,是今年看过最压抑的都市剧结局

唐嫣《爱情没有神话》37集大结局,是今年看过最压抑的都市剧结局

娱君坠星河
2026-05-14 10:23:32
5月15号后彻底翻身,家有哪些生肖麻烦少有,喜悦连续,个个赚钱

5月15号后彻底翻身,家有哪些生肖麻烦少有,喜悦连续,个个赚钱

毅谈生肖
2026-05-14 11:59:40
首个国有大行信用卡APP,即将关停

首个国有大行信用卡APP,即将关停

澎湃新闻
2026-05-13 22:53:05
G5骑士117-113逆转活塞 球员评价:4人优秀,3人及格,2人低迷

G5骑士117-113逆转活塞 球员评价:4人优秀,3人及格,2人低迷

篮球资讯达人
2026-05-14 11:12:47
死亡高峰还没有来!中国人口最大的坎不是少生,而是大批去世!

死亡高峰还没有来!中国人口最大的坎不是少生,而是大批去世!

番外行
2026-05-14 10:59:04
你们都是什么时候对男女之事开窍的?网友:果然还是拦不住有心人

你们都是什么时候对男女之事开窍的?网友:果然还是拦不住有心人

夜深爱杂谈
2026-02-21 21:37:02
为什么女教师成了单身女的最重灾区?网友剖析一针见血,我明白了

为什么女教师成了单身女的最重灾区?网友剖析一针见血,我明白了

夜深爱杂谈
2026-04-27 22:11:39
走路要看路噢!深圳一街道惊现巨蟒,足足2米长!蜷缩在路中间……

走路要看路噢!深圳一街道惊现巨蟒,足足2米长!蜷缩在路中间……

广东吃喝玩乐
2026-05-14 12:02:11
炸了!奥尼尔评 NBA 历史前十巨星 詹皇仅第 3

炸了!奥尼尔评 NBA 历史前十巨星 詹皇仅第 3

澜归序
2026-05-14 04:07:21
美媒披露:阿联酋秘密对伊朗发动军事打击

美媒披露:阿联酋秘密对伊朗发动军事打击

参考消息
2026-05-12 20:36:12
明星套现离场,留下85亿窟窿,华谊兄弟被谁“抽”干了?

明星套现离场,留下85亿窟窿,华谊兄弟被谁“抽”干了?

帅真商业
2026-05-06 21:23:01
毛主席83岁给华国锋的珍贵书法,练字真的有意义吗?

毛主席83岁给华国锋的珍贵书法,练字真的有意义吗?

书画相约
2026-05-11 10:09:02
小宝与王某雷,谁探访花的数量更多?

小宝与王某雷,谁探访花的数量更多?

挪威森林
2026-01-31 12:15:26
2026-05-14 14:04:49
摸鱼算法
摸鱼算法
致力于用最前沿的AI技术,换取更多发呆时间的三十岁青年。
2731文章数 26关注度
往期回顾 全部

科技要闻

马斯克:只有我和黄仁勋坐上了"空军一号"

头条要闻

十四世达赖的亲侄子被指诱奸超25名女孩 带女下属开房

头条要闻

十四世达赖的亲侄子被指诱奸超25名女孩 带女下属开房

体育要闻

登海报!哈登30+8+6创多项纪录 第8次赢天王山

娱乐要闻

肖战提名金海燕奖,这一步走得太稳

财经要闻

片仔癀依旧困在“片仔癀”

汽车要闻

C级纯电轿跑 吉利银河"TT"申报图来了

态度原创

家居
本地
时尚
公开课
军事航空

家居要闻

精神奢享 对话塔尖需求

本地新闻

用苏绣的方式,打开江西婺源

T恤+低腰阔腿裤、衬衫+低腰半裙,今年夏天最时髦的搭配,谁穿谁好看!

公开课

李玫瑾:为什么性格比能力更重要?

军事要闻

美以伊战争期间以总理密访阿联酋

无障碍浏览 进入关怀版