多租户(Multi-Tenancy)是SaaS中一个基础功能,本文介绍多租户下的数据库设计。

数据库中的层次结构

一般的关系型数据库中分三个层级:database.schema.table

  • database:即数据库实例,通过create database命令来创建数据库实例。体现到操作系统层面不同的数据库实例一般对应不同的目录。
  • schema:通过create schema命令来创建schema,一个数据库实例下面可以包含多个schema,不同schema中的表、索引、视图、存储过程等对象是隔离的。体现到操作系统层面不同的schema一般对应不同的目录。
  • table:就是表。

这里要多说一下的就是schema,不同数据库中的含义也不太一样。The ISO/IEC 9075-1 SQL 标准中是这样描述的:

defines a schema as a persistent, named collection of descriptors.

我觉得这个太抽象,不好理解。反倒是MySQL的文档里面对Schema的介绍比较明确:

Conceptually, a schema is a set of interrelated database objects, such as tables, table columns, data types of the columns, indexes, foreign keys, and so on. These objects are connected through SQL syntax, because the columns make up the tables, the foreign keys refer to tables and columns, and so on. Ideally, they are also connected logically, working together as part of a unified application or flexible framework.

总结一下就是schema就是一个容器,这个容器里面存放了一组相关的(interrelated)数据库对象。抛开这一点,其实Schema和数据库实例是非常相似的。不同的数据库对于Schema的支持程度也不太一样,MySQL中直接将schema与database等同,执行"create schema"相当于执行"create database"。Oracle、SQL Server中也都有Schema,但含义也都不一样。我个人觉得PostgreSQL(我最喜欢的RDBMS)里面的schema是最符合schema设计初衷的数据实现,这部分内容就不在本文介绍了。

另外再补充一下catalog这个概念,可以看到在关系型数据库里面是没有catalog的,但有的领域(比如大数据领域)是有的,也是用来做类似的层级划分的,一般是这样的层级:catalog.database.table。当然catalog这个词本身是目录、清单之类的意思,在很多类似这种的场景里面,都可以用这个词,所以往往还要结合场景去看。

多租户设计

因为数据库分了database、schema、table这三个层次(尽管并非所有数据库都实现了),所以多租户也有了三种比较常用的设计:

  1. database-based multitenancy:也称per-database-per-tenant,即一个租户一个数据库实例。
  2. schema-based multitenancy:也称per-schema-per-tenant,即一个租户一个schema,但都共享同一个数据库实例。
  3. table-based multitenancy:也称partitioned (discriminator) approach,即所有租户都使用一个表,然后通过在所有表中增加一个字段(通常就是租户id)来区分不同租户。数据库实例和表都是共享的。

审视一下三种设计方案,从1到3隔离程度越来越低,共享程度越来越高。我们从以下一些维度对比一下它们各自的优劣(注意:对比主要是从数据库角度看的,而不是整个SaaS):

  • 可扩展性:隔离度越高,扩展性越差。数据库实例在数据库中是一个比较重的资源,虽然RDBMS中一般没有对database的个数做限制,但一个数据库服务器上面创建成千上万个数据库实例的场景应该是很少见的吧。所以从1到3,扩展性依次变差。
  • 隔离性:主要是数据的隔离、负载的隔离。这个很明显,隔离性1最好,3最差,2适中。
  • 成本/资源利用率:这里的成本主要指数据库的成本,或者说硬件的资源利用率。隔离程度越高,利用率越差。比如很多业务其实都有业务高峰和低峰,如果能把高峰不在同一时间段的业务部署在一起,自然是能够提升资源的利用率。
  • 开发复杂度:主要体现在查询、过滤、database/schema/table切换等。1和2适中,3难度高一些。
  • 运维复杂度:性能监控、管理;database/schema/table的管理;租户数据恢复;容灾等。扩展其实也算运维的一部分,第一个已经讨论过了,这里就不包含扩展了。从监控、管理、租户数据恢复、容灾等考虑,隔离度越高,越简单。
  • 可定制性:根据不同租户的需求进行定制的难度,这个自然也是隔离度越高,定制化越好做。

实际中如何选择呢?这个要根据实际业务场景和各个方案的优劣进行选择了,没有完美方案,只有更适合你的方案。而且比如你选择了MySQL,那方案2就不存在了,因为MySQL中没有区分Database和Schema。还有这里只是比较学术的划分了一下设计方案,实际中一些大型SaaS会实现更复杂、灵活的多租户数据库方案,以平衡各个方案的优劣,这部分推荐一篇文章:Multi-tenant SaaS database tenancy patterns

最后要注意:不论哪种设计方式,不一定所有的数据都要存储在一个物理数据库服务器里面的,租户量、数据量多了以后,一般都是要分区(region)的。比如现在SaaS在欧洲、亚洲、非洲都提供服务,那显然不可能把三个洲所有租户的数据都放在同一个数据中心的数据库服务器上,而是各个洲都要建立各自的数据中心去承载服务。这种时候就需要应用层(或单独一个中间件层)根据用户的位置将请求先路由到对应的数据中心去,然后后续的处理方式每个数据中心就是一样的了,也就是SaaS层中的数据库层无需感知数据中心的差异。所以多租户的设计方案和数据中心分region(或部署多个物理数据库服务器)是两个维度,前者关注的是逻辑层的设计,后者关注的是物理层的分布。不要误认为设计方案中所有租户的数据共享一个表(方案3),那他们就一定要在同一个物理数据库服务器的一个表里面(方案1、2类似),只是说层级结构是一样的,比如用户表在A服务器上叫databaseA.schemaA.User,那在任何一个服务器上都叫databaseA.schemaA.User,这样对数据库操作代码来说,可以认为看到的就是“全量数据”,逻辑就是一套了。

理论总是抽象的,后面会再写几篇具体实现的文章,有兴趣的可以持续关注一下。

文章目录