技术点评:别每张表都加tenant_id
技术点评:别每张表都加tenant_id
前言
系统满足多租户需求,是很常见的场景。本文主要聊一下在维护旧系统过程中,发现的前人多租户方案中设计、实现不合理的地方。
背景
在维护旧系统时,踩了各种坑,终于忍不住在群里吐槽了下,于是有以下对话。
既然群友有疑问,我索性就整理了一下,把前因后果清楚。
正文
1.首先,租户数据隔离级别应该如何,没有唯一标准,评价只有是否合适。因此,逻辑隔离、物理隔离,都不是吐槽点。
2.原来的设计是,采取逻辑隔离方案,具体做法是,给每一张表都加上了tenant_id字段。问题就在于,有必要每一张表加吗?系统的权限设计是:先有租户,再有应用,用户、角色、资源、权限设置等内容都挂在应用下,所以,应用下的内容,关联 app_id 就行了,根本不需要tenant_id。
3.冗余多一个字段,会出现什么问题呢?先不提查询性能、存储空间等细节,就说很实际的场景:
3.1 每张表都加 tenant_id,几乎每条 sql 都要加 where tenant_id = ? ,那程序员会怎么做?首先想到的是用框架自动注入 sql
3.2 在后台管理端,有一个超级管理员,能够查询出所有租户的内容,也就是说,此时查询不能带 where tenant_id = ?,也即不能让框架注入 sql
要同时兼容上述逻辑,程序员又会怎么做呢?于是就引入了万恶的全局变量,类似于 injectSql = true,就添加 tenant_id 作为过滤条件。但默认是不是要 injectSql = true 呢?每个项目代码又不一样,你不运行,你都不知道。
更恶心的是,需求变化后,是否需要带上 tenant_id 的逻辑与原来不一致时,你得在某行代码执行前,手动设置 inejctSql 的值,在该行代码之后,再手动复原——因为如果不复原,作为全局变量,会影响到后面的代码!
md,这时候后你才会知道,还不如老老实实地设置 tenant_id,显示地设置,好过这种隐蔽的依赖。
4.但这还不是最难搞的。因为上述的是代码问题,真正难搞的是数据问题。考虑一种场景:超级管理员在后台管理某租户的应用,手动为租户添加数据,请问,新增的数据 tenant_id 的值是什么,某租户的 id,还是超级管理员的id?按逻辑来说,应该是某租户的 tenant_id。但问题在于,由于理解不同,或由于疏忽让框架自动注入了 tenant_id,导致上述场景,有些数据的 tenant_id 是超级管理员的id。而又因为超级管理员进行查询时,是不带 tenant_id 作为过滤条件的,因此即使 tenant_id 的值设置错误,依然在界面上能显示,使得这个问题一直存在着,旧数据一直被保留并使用。
5.现在,有需求要导出某个租户下的数据,结果发现 tenant_id 乱七八糟,你难不难受?
6.那么,梳理完逻辑链条,我认为,虽然某些程序员的在实现上犯了低级错误,但不是主要原因,罪魁祸首应该是设计上的懒惰。设计精细一点,明确好 tenant_id 到哪张表为止,也就没有后面的 sql 注入、数据错误那么多事了。所以,我才说,给租户隔离等于给每张表加 tenant_id 的设计很傻逼!