第二章 聚合数据模型
NOSQL技术与传统的关系数据库相比,最明显的转变是抛弃了关系模型。NoSQL主要有四种模型:
- 健值 - 面向聚合
- 文档 - 面向聚合
- 列族 - 面向聚合
- 图 - ?
前三种都是面向聚合的
2.1 聚合
关系模型:元组(行)是受限的结构:只能包含一系列的值,不能嵌套另外的元组和列表。
所有操作都以元组为目标,而且其返回值必须是元组。
面向聚合:是NoSQL操作数据时所用的单元,其结构比元组复杂,这种结构可以存放列表或嵌套其他记录。
聚合是“领域驱动设计(DDD)”中的术语: 把一组相互关联的对象视为一个整体单元来操作,这个单元叫聚合。一般通过原子操作更新聚合的值,并且在与数据存储通信时,也以聚合为单位。以聚合为单位来复制和分片比较自然。
2.1.1 关系模型和聚合模型示例
客户,订单,收货地址,账单的关系模型如下:
如果用聚合的话,如下图:主要有两个聚合:客户和订单。
客户与订单之间的关联不在某个聚合结构内部,它们算是两个聚合之间的关系。
这里ID为77的地址出现三次,在关系型中,ID在三个地方用(如果要拿到地址的具体值,需要用JOIN,优点是,地址变了,好维护。);在聚合中,地址的值被复制了三分(这样就不用JOIN表了,但是缺点是:地址改的话,要改很多地方)。
聚合中,产品名称直接写入订单项。这种做法在聚合模型中更常见,因为在数据交互的时候尽量减少所需访问聚合的个数(个人觉得这个和数据库中冗余列的设计思想类似,缺点是一样的: 如果产品名称发生变化了,如何去更新冗余列)。
其实还有另外一种方式画出聚合的边界,就是把客户下的全部订单放到客户聚合中:这样就只有一个聚合了。
对于如何划分聚合没有标准答案,取决于你想如何处理数据:如果想一次性访问客户全部订单,那就放到一个大的聚合里;反之,
2.1.2 面向聚合的影响
聚合的边界很难划定。
某种聚合模型,在有些场景下,很合适,在另外的场景下可能不合适。
因此在操作数据时,如果没有占主导地位的结构,采用聚合无知的关系型数据结构可能更适合。
聚合模型适合在集群上运行(聚合在理想情况下,代表一个对内高内聚,对外和其他聚合低耦合的结构,这样,每一个聚合独自部署在一个节点上,和其他节点的聚合不关联,这样可以降低分布式事务类的操作。)
一般,面向聚合的数据库不支持跨越多个聚合的ACID事务。取而代之的是,它每次只能在一个聚合结构上执行原子操作。如果想按照原子的方式操作多个聚合,就必须自己组织应用程序的代码。在实际中,大多数原子操作都可以局限于某个聚合结构内部。
2.2 健值数据模型与文档数据模型
二者都面向聚合(面向聚合的实质就是可以嵌套包含,而SQL数据库的列不能嵌套包含,也不能包含列表,所有SQL数据库必须用外键去关联,也就是用JOIN,面向聚合的数据库的理想效果是没有JOIN,在聚合内部包含所有的数据!)
两种模型的区别是:健值的聚合不透明,文档的聚合是透明的,其他的都很相似。健值的value可以是任意值,而文档的结构相对要严格一些,文档型的NoSQL可以访问特定的列,而健值数据库的value是个整体,不够灵活。(也就是:健值数据库,要访问聚合内容,只能通过健来查找。而文档型数据库,则可以用聚合中的字段查询,可以只获取一部分聚合,而不用获取全部内容。此外,数据库还可以按照聚合内容创建索引)。
二者界限模糊:但是还是有区别的:健值数据库,基本上都是通过健来搜索聚合内容;而文档型数据库中,我们提交的查询关键词往往是基于文档的内部结构的。
2.3 列族存储
"列存储数据库":大部分数据库以行为单元存储数据,尤其是在需要提高写入性能的场合更如此。然而,有些情况下写入操作执行得很少,但是经常需要一次性读取若干行中的很多列。这种情况下,将所有行的某一组列作为基本数据存储单元,效果会更好。
“列族模型”: 可以看作是两级聚合,第一级类似健值存储,第二级可以包含更加详细的信息。
列族数据库将列组织为列族。每一列都必须是某个列族的一部分,而且访问数据的单元也得是列。这样设计的前提是,某个列族中的数据经常需要一起访问。
列族可以包含超列,也就是列里面可以嵌套列。
2.4 总结
三种NoSQL的共同点是:都是用聚合这一概念。聚合都有一个索引键。集群上运行时,聚合是中心环节,数据库保证聚合内的数据放在通过一个节点上。聚合还是更新操作的最小数据单元,对事务控制来说,以聚合为操作单元,正好合适。
三者差别:键值数据模型的聚合是不透明的整体;文档模型的聚合是透明的,可以查询获得一部分数据。不过,文档没有模式,因此想优化存储并获取聚合中的部分内容时,数据库不太好调整文档结构。