多租户数据库:为什么在每个表中都要加入TenantID列?

26

我看过的所有关于多租户数据库模型的教程都告诉你要在每个表中加入TenantID:

zoos
-------
id
zoo_name
tenant_id

animals
-------
id
zoo_id
animal_name
tenant_id

但是,我认为这样做有点多余。为什么不仅将tenant_id列添加到zoos表中,并利用zoosanimals之间的外键关系?

您是否会将tenant_id添加到每个表中只是为了避免连接变得太复杂?它是防止错误的安全保护措施吗?还是出于性能考虑?


1
在设计多租户数据库时要考虑一些因素,请参阅MSDN文章:多租户数据架构 简而言之,有几种方法可供选择,这是一系列的选择和后果。根据需求做出明智的决策。 - Hendy Irawan
6个回答

16
如果你的一个关键设计因素是安全性 - 具体来说,一个客户端不能以任何方式访问另一个客户端的数据 - 那么,根据你实现这种安全性的方式,在每个表中添加一个限定列可能是必要的。其中一种策略在这里描述,需要在每个表上构建一个视图;假设每个表都包含一个tenantId列,则如果正确配置,每个视图都可以包含一个"WHERE tenantId = SUSER_SID()"子句(当然,你需要配置数据库使得客户端只能访问视图)。
另一个因素(就像我目前的工作一样)是加载仓库数据(ETL)。表按tenantId进行分区(我们使用表分区,但分区视图也可以起作用),可以轻松地为客户端加载或卸载数据,而不会严重影响其他客户端。
但是像往常一样,这里涉及到很多“取决于”因素。如果没有明确和现有需求未来需求的可能性极低,则将该列标准化。只要意识到这更多是物理实现而不是概念或逻辑数据库设计的措施即可。

12

这是出于方便和性能的考虑 - 在规范化方面,您是完全正确的,它只需要在顶部插入。问题是,要获取一些数据(例如 zoo -> animal -> food -> supplier),你必须对概念上非常简单的查询进行可怕的复杂连接。

因此,在现实世界中,人们不得不做出妥协 - 然后问题就变成了在哪里以及在多大程度上做出妥协。

请参阅本文也许规范化不是常态 - 以及它的结论:

正如古老的格言所说,规范化到痛处,反规范化到可行为止

作为探索该主题的起点


冒着开始一场程序员宗教战的风险,我不禁想知道这是否是自然键会非常合适的一个例子。如果您的多租户应用程序按域名进行分区,并将租户ID设置为域名,那么也可以通过这种方式减少连接。 - Paul
它不会减少连接 - 你仍然面临在多个级别包含键(概念上不好)或复杂连接(同样概念上不好)的问题,而且无论如何,自然键几乎总是会反咬你一口 (-: - Murph
1
如果您将多租户共享关系规范化为3NF或5NF,则每个表中都会有租户标识符。 - Mike Sherrill 'Cat Recall'

9
如果我将tenantID放在层次结构的顶部(即在动物园级别),则需要考虑以下几个问题:
  1. 层次结构的顶部永远不能改变,例如如果您需要在树的上方添加一个节点(例如区域 -> 动物园 -> 动物),则每次都会强制重新组织。
  2. 对于某些查询,您将被迫从层次结构的顶部开始,即要求列出所有可用动物的列表将强制您从树的顶部开始。
  3. 为什么不使用模式?每个租户都在其自己的模式中被隔离。这也会很好地分离数据集。

7
首先想到的是查找“animals > zoos > tenants”比仅查找“animals > tenants”要慢。而且很可能你会经常进行这样的查找(例如,“获取某个租户的所有动物,无论在哪个动物园中”)。
对于小型到中型应用程序,您可以使用更规范化的结构,但出于效率考虑,您应该选择冗余数据(一般来说,多租户应用程序不是小型应用程序)。只需确保它不会“失步”,这是具有冗余数据的风险。
回答您最后一段的问题,原因很简单,就是性能。连接并不是坏事;它们帮助您将数据保存在一个地方,而不是三个地方。这绝对不是为了防止错误。向更多表添加tenant_id字段会增加错误的风险(尽管对于一个从不更改的id而言,这不会成为太大的问题)。

1
将租户ID仅存储在动物园表中并不是“更规范化”的做法。在每个表中存储它作为外键并不是多余的;这正是外键的使用方式。 - Mike Sherrill 'Cat Recall'
1
外键并不是多余的。它们是关系模型的核心和独特特征。在多租户、共享架构中,所有的外键都是由tenant_id加上其他内容组成的复合键。那个租户ID是唯一能够区分一个租户的行与其他租户的行的东西。如果在表中省略了租户ID,你也必须禁止插入、更新和删除操作。(请花一分钟思考一下。) - Mike Sherrill 'Cat Recall'
1
我的意思是“重复数据”,而不是“不必要的”意义上的冗余。 外键有用的功能,我同意。 不过,我不认为它们对于数据模型来说是必需的。 如果一个动物属于一个所属于租户的动物园,则所有数据都可以推断出该动物属于该租户。 我想你误解了我的意思,似乎你认为我在说租户ID字段根本不应该存在。 我只是说它只需要存在两次(租户表和动物园表)。 在动物表中拥有它将是冗余的,但更有效率的读取。 - Blixt
1
TenantId作为“外键”似乎不是冗余数据。 - Denis Kucherov
2
我认为这只是一个语言问题。可以推断出的任何值都是“冗余”的,也就是“去规范化”的数据。通常出于性能原因而保留。在这种情况下,“tenant_id”是去规范化冗余的,因为它存储了两次,如果您意外更改其中一个而不更改另一个,则会发生数据不一致/损坏。完全规范化的数据库不允许这样做。请阅读:https://en.wikipedia.org/wiki/Database_normalization - Blixt
显示剩余3条评论

0

嗯,鲍勃可能在1号动物园拥有一只长颈鹿,而乔可能在同一动物园拥有一只狮子。 他们不应该查看彼此的数据。


-2

N1 的原因是为了安全。

在多租户应用程序中,安全性需要是一个强大的概念。

假设您让用户有能力修改动物。 您创建一个表单,其中包含一个下拉列表,显示当前租户的动物园。 如果用户黑掉表单并传递另一个租户的动物园 ID,会发生什么?

动物将被移到另一个租户的动物园!

这是多租户应用程序中的真正痛苦!


1
您可以在服务器端进行检查,以确保用户有权访问该ID。无论数据库设计如何,您都需要这样做,但我理解您的观点。 - Mike Sickler
3
如果用户入侵表单并提供属于其他承租人的动物ID会发生什么?如果用户入侵“我的个人资料”页面并提供超级管理员账户的ID会发生什么?您不能简单地相信用户的输入 - 这适用于任何具有不同权限的用户的系统,而不仅仅是多租户系统。 - Oskar Berggren
1
这在认证过程中得到了缓解,客户端不知道tenant_id。tenant_id仅在服务器端显而易见。因此,在用户手动传递tenant_id的情况下,它是无用的,因为tenant_id不是从客户端检索而是从服务器逻辑中的应用状态变量获取的。 - Dohd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接