我正在尝试找出设计将来需要进行水平分区的多租户数据库模式时的最佳方法。
关于数据库的一些粗略数字..
租户总数约为10,000人。每个租户存储的数据量从500MB到3GB不等。租户的数量一开始很小,几年后会增长到10,000个,所以最初我们可以从一个多租户数据库开始,但从长远来看,这将需要横向扩展以提高性能的原因。
更新-一个复杂的因素是偶尔租户(公司)可以合并在一起,我也需要支持这一点…,
多租户将使用本文中描述的共享数据库、共享模式体系结构来实现http://msdn.microsoft.com/en-us/library/aa479086.aspx
考虑到将来我们将面临水平分区,并且在事情解决之前我们可能会将客户端从一个数据库移动到另一个数据库多次,我认为最好在每个表上使用GUID作为主键以及唯一的tenantID列。
我知道使用GUID作为主键有性能开销,但这是我需要接受的折衷吗?将来有没有别的方法来设计水平分区?
这里有一个例子-假设我想在未来合并租户为100和200的公司,如果PK是一个整数,那么当我将行从数据库2复制到数据库1时可能会发生冲突,使用{guids}可以保证不会发生冲突…
数据库1数据库2租户,id, description租户,id, description100,1, 'foo' 200,1, 'xxx'100,2, 'boo' 200,2, 'yyy'
数据库1数据库2租户,id, description租户,id, description100,{aaa}, 'foo' 200,{ccc}, 'xxx'100年,{bbb},"嘘 ' 200年,{ddd},"多"
GUIDs似乎是主键的自然选择—如果您确实必须这样做,您可能会争论将它用于表的primary key。我强烈建议不要这样做是使用GUID列作为集群键, SQL Server默认是这样做的,除非你特别告诉它不要这样做。
你真的需要把两个问题分开:
1) 主键是一个逻辑结构——唯一且可靠地标识表中每一行的候选键之一。这可以是任何东西,真的——INT, GUID,字符串——选择对你的场景最有意义的。
2) 集群键(定义表上的"聚集索引"的一列或多列)——这是一个物理存储相关的东西,在这里,一个小的、稳定的、不断增长的数据类型是你最好的选择——INT或BIGINT作为你的默认选项。
默认情况下,SQL Server表上的主键也被用作集群键-但这并不需要那样!我个人认为,将以前基于GUID的主/集群键分解为两个单独的键——GUID上的主(逻辑)键和单独的INT IDENTITY(1,1)列上的集群(排序)键,可以获得巨大的性能提升。
正如索引女王Kimberly Tripp和其他人多次指出的那样,GUID作为集群键并不是最优的,因为它的随机性会导致大量的页面和索引碎片,并且通常会导致糟糕的性能。
是的,我知道-有newsequentialid()
在SQL Server 2005及以上-但即使这不是真正的和完整的顺序,因此也遭受相同的问题作为GUID -只是有点不那么突出。
然后还有另一个需要考虑的问题:表上的集群键将被添加到表上的每个非集群索引的每个条目中-因此您确实希望确保它尽可能小。通常,拥有20多亿行的INT对于绝大多数表来说应该足够了——与GUID作为集群键相比,您可以在磁盘和服务器内存上节省数百兆字节的存储空间。
快速计算-使用INT与GUID作为主键和群集键:
- 基本表,1' 000000行(3.8 MB vs. 15.26 MB)
- 6个非聚集索引(22.89 MB vs. 91.55 MB)
TOTAL: 25 MB vs. 106 MB—这还只是在一个表上!
更多发人深省的东西——金伯利·特里普的优秀作品——读一遍,再读一遍,消化它!这是SQL Server索引的福音,真的。
- GUIDs作为主键和/或集群键
- 聚类索引争论仍在继续
- 不断增加的聚类关键-聚类索引辩论..........再次!
- 磁盘空间便宜-这是而不是的重点!
Marc
这里有两点需要注意:
- 在所有行 中标识租户的行集
- 在所有行中标识租户行中的一行
点2是主键。
使用GUID来标识租户是很有用的,因为您无法猜测到另一个租户的ID(就像在白皮书中使用IDENTITY一样)。但是集群键的GUID不是一个好主意(根据marc_s的回答)。
这导致GUID和IDENTITY列的复合PK,可能是
- 将IDENTITY首先作为唯一的聚集索引,
- GUID是租户表的FK,非聚集索引
- 和两列上的PK,但非集群
这应该是一个合理的折衷,以覆盖该表的大多数查询模式和fk。
当然,这取决于最终的设计:我在这里假设这是某种"事实"或"事实的父"表
您考虑过SQL Azure联合吗?向外扩展水平分区,包括支持开箱即用的重新平衡操作。跨分区移动租户),并提供了高可用性的服务解决方案。在SQL Server box产品上根本没有类似的东西。参见在SQL Azure中介绍联邦或如何使用SQL Azure进行分片。
关于让每个实体PK成为GUID的问题,我真的不明白这一点。让每个租户数据表都以租户ID为前缀是的,绝对是这样。使所有租户数据聚集索引的最左边键为tenantId
: (tenantId, key, key, key))
:同上,必须。使实体主键为(tenantId, entityId)
:非常有可能。但是让entityId
成为一个向导?我真不明白为什么。除非租户之间有共享的实体,否则tenantId
就像entityId
应用的名称空间一样。跨分片移动数据是可以的,即使它会导致重复的entityId
值,因为实体PK是由tenantId
作用域。