Azure CosmosDB (13) CosmosDB数据建模 Windows Azure Platform 系列文章目录

  《Windows Azure Platform 系列文章目录html

 

  咱们在使用NoSQL的时候,如Azure Cosmos DB,能够很是快速的查询非结构化,或半结构化的数据。咱们须要花一些时间,研究Cosmos DB的数据建模,来保证查询性能和可扩展性,同事下降成本。web

 

  阅读完这篇文章后,咱们将学会:数据库

  1.什么是数据建模,为何咱们要关注数据建模数组

  2.如何在Azure Cosmos DB进行数据建模,与传统关系型数据库有什么不一样服务器

  3.如何在非关系型数据库中,保存关系型数据dom

  4.何时执行嵌入(embed)数据,何时执行链接(link)数据post

 

  嵌入(embed)数据性能

  当咱们开始在Cosmos DB进行数据建模的时候,尝试对咱们的数据实体(Entity)视为自包含(Self-contained items)并保存在JSON文件中优化

  为了比较,让咱们首先看一下如何在关系型数据库中进行数据建模。下面的案例将介绍咱们在关系型数据库中,如何保存用户信息this

  

  当咱们使用关系型数据库的时候,通常都须要将数据规范化(Normalize)。规划范数据通常都会引入数据实体,好比一我的,咱们能够将用户信息分解为不一样的属性信息。

  在上面的例子中,一我的有多个联系人,也有多个地址。联系人的详细信息能够进一步进行分解并提取经常使用字段。咱们也能够用一样的方法,对地址进行分解,好比地址的类型能够是家庭地址,或者是公司地址。

  规范化数据的指导方法是避免存储冗余的数据而且应用数据。在上面的示例中,咱们若是要读取一我的的全部联系人的详细信息和地址,咱们须要使用JOIN方法,查询到所须要的数据:

SELECT p.FirstName, p.LastName, a.City, cd.Detail
FROM Person p
JOIN ContactDetail cd ON cd.PersonId = p.Id
JOIN ContactDetailType on cdt ON cdt.Id = cd.TypeId
JOIN Address a ON a.PersonId = p.Id

  若是咱们更新一我的的联系人信息和地址,须要跨多张表执行更新操做

 

  如今咱们看看如何在Azure Cosmos DB使用自包含(Self-contained items)实体

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "addresses": [
        {
            "line1": "100 Some Street",
            "line2": "Unit 1",
            "city": "Seattle",
            "state": "WA",
            "zip": 98012
        }
    ],
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555", "extension": 5555}
    ]
}

  上面咱们使用了非规范化(denormalized)来保存人的记录,咱们将与人相关的全部信息,好比联系人的详细信息和地址信息,嵌入到单个JSON文档中,从而对人的记录进行了非规范化。另外,由于咱们不局限于固定的Schema,咱们能够灵活的使用不一样类型的联系人信息

  从Cosmos DB中读取一条记录,如今只须要单个读取操做。更新人的记录,包括联系人信息和地址信息,也只须要一次写入的操做。

  经过使用非规范化(denormalized)保存数据,相比传统的关系型数据库,咱们的应用程序的读取和更新的操做能够减小。

 

  何时使用嵌入(embed)数据

  咱们通常在如下状况下,使用嵌入数据:

  1.数据实体之间有包含(contained)关系

  2.数据实体之间有1对多的关系

  3.嵌入(embed)数据不常常变化

  4.嵌入的数据不会无限增加

  5.嵌入的数据是频繁集中查询的

  一般非规范化(denormzlized)数据模型具备更好的读取性能

 

  何时不使用嵌入(embed)数据

  虽然Azure Cosmos DB中的经验法则是对全部内容进行非规范化,并将全部数据嵌入到单个项目中,但这可能会致使某些状况:

  咱们观察下面的JSON:

{
    "id": "1",
    "name": "What's new in the coolest Cloud",
    "summary": "A blog post by someone real famous",
    "comments": [
        {"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
        {"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
        …
        {"id": 100001, "author": "jane", "comment": "and on we go ..."},
        …
        {"id": 1000000001, "author": "angry", "comment": "blah angry blah angry"},
        …
        {"id": ∞ + 1, "author": "bored", "comment": "oh man, will this ever end?"},
    ]
}

  若是咱们对一个博客系统进行建模,上面的例子就是采用嵌入(embed)数据方法,存储评论(comments)数据。上面例子的问题是评论的数据是没有限制的,这意味着任何一个发布的POST的内容,都有无限多个评论数据。这会让Cosmos DB的JSON文件变的无限大,可能会产生问题。

  随着Cosmos DB的数据尺寸变的愈来愈大,读取数据和更新数据可能会产生影响。

  在这种状况下,咱们最好能够考虑采用如下的数据建模。

Post item:
{
    "id": "1",
    "name": "What's new in the coolest Cloud",
    "summary": "A blog post by someone real famous",
    "recentComments": [
        {"id": 1, "author": "anon", "comment": "something useful, I'm sure"},
        {"id": 2, "author": "bob", "comment": "wisdom from the interwebs"},
        {"id": 3, "author": "jane", "comment": "....."}
    ]
}

Comment items:
{
    "postId": "1"
    "comments": [
        {"id": 4, "author": "anon", "comment": "more goodness"},
        {"id": 5, "author": "bob", "comment": "tails from the field"},
        ...
        {"id": 99, "author": "angry", "comment": "blah angry blah angry"}
    ]
},
{
    "postId": "1"
    "comments": [
        {"id": 100, "author": "anon", "comment": "yet more"},
        ...
        {"id": 199, "author": "bored", "comment": "will this ever end?"}
    ]
}

  上面的数据模型中,在一个Container中,包含了最新三个评论,且评论具备固定的属性。

  其余的评论信息是保存在单独的Container中,每一个container保存100条数据。Batch的大小设置为100,是由于咱们假设应用程序容许用户一次加载100条评论数据

   

  另外的场景中,嵌入(embed)数据并非一个好的主意,好比嵌入的数据须要常常跨项目使用,且常常发生变化

  咱们能够参考下面的JSON内容:

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        {
            "numberHeld": 100,
            "stock": { "symbol": "zaza", "open": 1, "high": 2, "low": 0.5 }
        },
        {
            "numberHeld": 50,
            "stock": { "symbol": "xcxc", "open": 89, "high": 93.24, "low": 88.87 }
        }
    ]
}

  这个场景是我的投资的股票信息。咱们选择将股票信息嵌入到每一个投资组合文档中。在一个相关数据频繁变化的环境中,如股票交易应用程序,嵌入频繁变化的数据意味着您每次交易股票时都会不断更新每一个投资组合文档。

  股票zaza可能在一天内被交易数百次,成千上万的用户能够在他们的投资组合中拥有zaza。 使用上述数据模型,咱们天天必须屡次更新数千个投资组合文档,致使系统没法很好地扩展。

 

 

  引用数据 (Referencing data)

  所以,在大多数状况下使用嵌入(embed)数据能够很好的处理业务场景,可是很明显在某些场景下,非规范化数据将致使更多的问题而得不偿失。咱们如今应该怎么办?

  关系型数据库不是在数据数据实体之间建立关系的惟一选择。在Document Database中,咱们能够在一个Document中建立对另一个Document的引用。咱们并非说使用 Azure Cosmos DB能够更好的适应关系型数据库,或者其余Document Database。咱们仅仅说明在Azure Cosmos DB中也可使用简单的关系,而且颇有用。

  在下面的JSON文档中,咱们选择以前的股票投资组合的示例,可是咱们采用了引用数据的关系,而不是嵌入数据(embed)。在这种状况下,当一天中股票信息发生频繁变化的时候,咱们只须要更新股票的Document。

Person document:
{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        { "numberHeld":  100, "stockId": 1},
        { "numberHeld":  50, "stockId": 2}
    ]
}

Stock documents:
{
    "id": "1",
    "symbol": "zaza",
    "open": 1,
    "high": 2,
    "low": 0.5,
    "vol": 11970000,
    "mkt-cap": 42000000,
    "pe": 5.89
},
{
    "id": "2",
    "symbol": "xcxc",
    "open": 89,
    "high": 93.24,
    "low": 88.87,
    "vol": 2970200,
    "mkt-cap": 1005000,
    "pe": 75.82
}

  不过, 这种方法的一个直接缺点是, 若是您的应用程序须要显示有关在显示一我的的投资组合时持有的每只股票的信息;在这种状况下, 您须要屡次访问数据库以加载每一个库存文档的信息。在这里, 咱们决定提升写入操做的效率, 这些操做在一天中频繁发生, 但反过来又影响了对此特定系统的性能影响较小的读取操做。

   规范化数据模型可能须要屡次访问服务器

 

  外键在哪里?

  在Document Database中并不存在约束,外键或其余相似概念。因此在Document Database中,任何Document之间的关系都是“弱连接”的关系,而且Document Database不会验证这些关系。若是想要确保文档要引用的数据实际存在,则需在应用程序中进行此验证,或经过使用 Azure Cosmos DB 上的服务器端触发器或存储过程来验证。

 

  何时使用引用?

  咱们通常在如下状况下,使用引用数据:

  1.一对多的关系

  2.多对多的关系

  3.数据须要频繁更改

  4.使用数据可能没有限制

  一般规范化可以提供更好的编写性能。

 

 

  将关系存储在哪里?

  关系的增加将有助于肯定用于存储引用的文档。

  让咱们看看下面的对出版商和书籍进行建模的 JSON 代码。

Publisher document:
{
    "id": "mspress",
    "name": "Microsoft Press",
    "books": [ 1, 2, 3, ..., 100, ..., 1000]
}

Book documents:
{"id": "1", "name": "Azure Cosmos DB 101" }
{"id": "2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "3", "name": "Taking over the world one JSON doc at a time" }
...
{"id": "100", "name": "Learn about Azure Cosmos DB" }
...
{"id": "1000", "name": "Deep Dive into Azure Cosmos DB" }

  

  若是每一个出版商的书籍数量较少且增加有限,那么在出版商文档中存储书籍引用可能颇有用。 可是,若是每一个出版商的书籍数量没有限制,那么此数据模型将产生可变、不断增加的数组,相似于上面示例中的出版商文档。

  稍微作些更改就会使模型仍显示相同的数据,但能够避免产生较大的可变集合。

Publisher document:
{
    "id": "mspress",
    "name": "Microsoft Press"
}

Book documents:
{"id": "1","name": "Azure Cosmos DB 101", "pub-id": "mspress"}
{"id": "2","name": "Azure Cosmos DB for RDBMS Users", "pub-id": "mspress"}
{"id": "3","name": "Taking over the world one JSON doc at a time"}
...
{"id": "100","name": "Learn about Azure Cosmos DB", "pub-id": "mspress"}
...
{"id": "1000","name": "Deep Dive into Azure Cosmos DB", "pub-id": "mspress"}

  在上面的示例中,咱们删除了出版商文档中的无限制集合, 只在每一个书籍文档中引用出版商。

 

  如何处理多对多关系(Many: Many)进行数据建模

  在关系型数据库中,多对多关系一般使用表链接来实现,表链接就是将其余表的记录链接在一块儿

  

  

   可能想要使用文档复制相同内容,并生成相似如下示例的数据模型。

Author documents:
{"id": "a1", "name": "Thomas Andersen" }
{"id": "a2", "name": "William Wakefield" }

Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101" }
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users" }
{"id": "b3", "name": "Taking over the world one JSON doc at a time" }
{"id": "b4", "name": "Learn about Azure Cosmos DB" }
{"id": "b5", "name": "Deep Dive into Azure Cosmos DB" }

Joining documents:
{"authorId": "a1", "bookId": "b1" }
{"authorId": "a2", "bookId": "b1" }
{"authorId": "a1", "bookId": "b2" }
{"authorId": "a1", "bookId": "b3" }

  

  此模型可行。 可是,加载一个做者及其书籍或加载一个书籍及其做者,将始终要求对数据库执行至少两次查询。 一次是对联接文档的查询,另外一个查询用来获取联接的实际文档。

  若是联接表只是将两个数据片断联接在一块儿,那么为何不将该表彻底删除? 请考虑如下代码。

Author documents:
{"id": "a1", "name": "Thomas Andersen", "books": ["b1, "b2", "b3"]}
{"id": "a2", "name": "William Wakefield", "books": ["b1", "b4"]}

Book documents:
{"id": "b1", "name": "Azure Cosmos DB 101", "authors": ["a1", "a2"]}
{"id": "b2", "name": "Azure Cosmos DB for RDBMS Users", "authors": ["a1"]}
{"id": "b3", "name": "Learn about Azure Cosmos DB", "authors": ["a1"]}
{"id": "b4", "name": "Deep Dive into Azure Cosmos DB", "authors": ["a2"]}

  如今,若是我有做者的姓名,我能够当即知道他们所写的哪些书,相反若是我有一个书籍文档加载我能够知道做者的 Id。 这能够省去对联接表的中间查询,从而减小了应用程序须要往返访问服务器的次数。

 

  混合数据建模

  如今咱们已经看了嵌入数据(或非规范化)和引用数据(规范化)的示例,正如咱们看到的每种方法都有其优势和缺点。

  不须要始终只使用其中一种方法,能够大胆地将这两种方法结合使用。

  根据应用程序的特定使用模式和工做负载,可能在一些状况下结合使用嵌入式数据和引用数据是有意义的,可产生具备更少的服务器往返访问次数的更简单的应用程序逻辑,同时仍保持较好的性能级别。

  请考虑如下 JSON。

Author documents:
{
    "id": "a1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "countOfBooks": 3,
    "books": ["b1", "b2", "b3"],
    "images": [
        {"thumbnail": "https://....png"}
        {"profile": "https://....png"}
        {"large": "https://....png"}
    ]
},
{
    "id": "a2",
    "firstName": "William",
    "lastName": "Wakefield",
    "countOfBooks": 1,
    "books": ["b1"],
    "images": [
        {"thumbnail": "https://....png"}
    ]
}

Book documents:
{
    "id": "b1",
    "name": "Azure Cosmos DB 101",
    "authors": [
        {"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
        {"id": "a2", "name": "William Wakefield", "thumbnailUrl": "https://....png"}
    ]
},
{
    "id": "b2",
    "name": "Azure Cosmos DB for RDBMS Users",
    "authors": [
        {"id": "a1", "name": "Thomas Andersen", "thumbnailUrl": "https://....png"},
    ]
}

  此处咱们(主要)遵循了嵌入式模型,在顶层文档中嵌入其余实体的数据,但同时引用了其余数据。

   若是查看书籍文档(Book)中的做者数组,会看到一些有趣的字段。 在Document Book中,咱们有一个authors:id字段,经过id字段咱们能够查到找Document Author中的信息,这是一个标准的规范化模型。可是咱们在Document Book中,还包含了name和thumbnailUrl字段。咱们能够经过Document Book中的authors:id字段,查找到Document Author中的其余属性。由于在这个应用程序中,咱们须要在每一本书中显示做者的名称和做者的缩略图,因此使用非规范化(denormalizing)的方式,把做者的名称和做者的缩略图,预先保存到Document Book中,减小额外的传输和IO开销。

  固然,若是做者的名称更改,或者他们想要更新本身的照片,咱们将须要转并更新他们曾经发布,但咱们的应用程序,基于做者不常常更改其名称的假设每本书,这是一个可接受的设计。

  在示例中预先计算的聚合值可在读取操做上节省高昂的处理成本。 在本例中,做者文档中嵌入的一些数据为在运行时计算的数据。 每当出版了一本新书,就会建立一个书籍文档而且将 countOfBooks 字段设置为基于特定做者的现有书籍文档数的计算值。 这种优化对于读取频繁的系统来讲是有益的,为了优化读取,咱们能够对写入操做执行更多计算。

  由于 Azure Cosmos DB 支持多文档事务,因此构建一个具备预先计算字段的模型是可能的。许多 NoSQL 存储没法跨文档执行事务,正是由于该限制,因此提倡诸如“始终嵌入全部数据”的设计决策。 在 Azure Cosmos DB 中,可使用服务器端触发器或存储过程在一个 ACID 事务中插入书籍和更新做者信息等。 如今无需将全部数据嵌入一个文档,只需确保数据保持一致性。

 

  区分不一样的文档类型

  在一些场景中,咱们可能须要在一个Collection中,保存不一样类型的文档。这一般是这种状况,若是但愿多个相关的文档中保存在相同的分区。 例如,能够将这两个丛书和同一集合中的书评和分区经过bookId。 在这种状况下,你一般想要添加到文档中使用字段,用于标识其类型以区分它们。

Book documents:
{
    "id": "b1",
    "name": "Azure Cosmos DB 101",
    "bookId": "b1",
    "type": "book"
}

Review documents:
{
    "id": "r1",
    "content": "This book is awesome",
    "bookId": "b1",
    "type": "review"
},
{
    "id": "r2",
    "content": "Best book ever!",
    "bookId": "b1",
    "type": "review"
}
相关文章
相关标签/搜索