当前位置: 首页 > 科技观察

如何将关系型数据导入MongoDB?

时间:2023-03-14 22:12:56 科技观察

Preparation关系数据库在数据存储领域占据主导地位超过30年。尽管NoSQL数据库诞生于2000年后,但它的出现并没有改变关系型数据的霸主地位。随着近年来互联网应用的迅速兴起和互联网用户的不断增加,数据源变得越来越复杂多样,传统的关系型数据存储面临着巨大的挑战。这种挑战体现在数据格式死板、修改困难、存储不灵活、扩展困难等方面。因此,很多企业和公司陆续将数据从关系型迁移到NoSQL,其中MongoDB的实现使用范围比较广。本文将与大家分享将关系型数据导入MongoDB时应该遵循的步骤和注意事项。在考虑将关系型数据导入NoSQL时,首先要确认的是:这个导入过程不会是全自动的,不是备份数据、迁移数据、背几个命令那么简单;其次,这个过程不是一个纯粹的技术问题。在制定具体计划时,项目经理、业务分析师、开发人员和数据库管理员都应该参与计划的讨论。迁移方案、技术方案、每个项目负责人的职责都应该在所有人员在场的情况下明确制定;最后,还要考虑迁移失败后的恢复计划,根据应用数据的复杂程度来确定迁移的工作量。它也不会完全一样。上图列出了一个项目从关系型数据迁移到NoSQL的一般步骤。当然,这绝对不是唯一的标准。这只是一种普遍的做法,根据不同项目的特殊需要,可能会有一些调整。下面具体分析一下各个阶段的具体工作内容。数据模型定义大家可能会觉得奇怪,MongoDB不是结构无关的NoSQL数据库吗?为什么要提到数据库表结构定义。实际上,NoSQL中的结构,从技术角度来说,数据库对表结构没有强约束,任何格式的JSON都可以插入到MongoDB表中。但是,我们在做项目的时候,不能随心所欲地向数据库中插入数据,必须遵循自己定义的一套规则,否则程序将无法在数据层面管理好业务逻辑。在讨论表结构之前,我们先看一下MongoDB中一些术语与关系型数据库的对应关系。这似乎很容易理解。在MongoDB中,我们称表Collection,表中每一行的数据称为Document。其他的基本沿用关系型数据库的命名。在MongoDB中,所有的数据格式都使用JSON作为数据库类型,可以更加灵活的存储各种数据库关系。这就是为什么MongoDB可以将各种结构的数据存储在一个Collection中。比如你可以在MongoDB中插入这样一个JSON:{"user":{"name":"张三",}},然后再插入这个JSON{"product":{"id":"00001"}}.可以看出,这两个json没有关系,没有任何相同的属性,但是在MongoDB中都是合法的数据,可以同时存在于一个Collection中。当然我们不鼓励大家这样做,因为维护你的数据库表很困难,而且查询索引也很麻烦,会产生很多不必要的索引存储。我们所说的灵活结构是指在一个结构框架的基础上,可以在不重新定义数据模式的情况下,灵活地扩展和添加新的数据。因此,我们需要讨论在数据库迁移之前如何定义Collection的结构。MongoDB将JSON存储在称为BSON的数据结构中。BSON是指BinaryJSON,二进制JSON,在JSON的基础上增加了一些新的数据类型,如int、float、long。JSON格式可以灵活地存储嵌入式数据结构,以及数组,这在关系型数据库中是很难想象的。在定义Collection结构时,需要根据应用的实际需要找出数据模型的定义,充分利用MongDB的存储灵活性。例如,下面是一个典型的二对多数据库表。学生表:成绩表:其中,第一张表是学生表,第二张表是学生成绩表,一个学生可以有多门课程,所以他们之间是一对多的关系,其中studnet_id在student表中的主键对应于grade表中的外键。这种表示方式在关系型数据库中是绝对正确的,但在MongoDB中可能是另一种存储方式。为了充分利用JSON格式的内嵌存储,我们通常将这种关系存储在Collection中的一个记录(Document)中,如下图:上面是学生张扫描的记录存储,可以看到我们把学生成绩作为学生表的内嵌字段,因为是一对多的关系,所以我们用数组的形式存储。这种基于JSON文档的存储结构具有以下优点:数据一目了然。当你从数据库中取出一条学生记录时,会显示该学生的所有基本信息。方便大家阅读浏览。避免了多个数据库表连接操作。在关系型数据库中,表与表之间存在各种链接操作,如左右连接、内连接、外连接等。为了找到一个学生的所有信息,我们可能需要连接几个表来得到想要的数据。除了需要编写更复杂的SQL语句外,数据库的性能也会受到影响。数据库在进行连接操作时,内部可能需要从磁盘的不同位置读取数据,增加了IO操作。对比MongoDB,一次查询只需要读一次磁盘,大大提高了查询效率。删除、修改操作简单方便。如果所有相关的学生信息都存储在一个Collection中,那么学生信息的删除和修改只需要在一张表中进行即可。试想一下,在关系型数据库中,如果需要删除一条学生记录,可能需要操作学生表、年级表、宿舍表,以及与该学生相关的所有表。这种设计对于关系数据库开发人员来说是个大问题。问题。如果做得不好,数据库中会存储大量过时无效的数据,这些数据可能成为永远无法访问的死胡同。所有Document都是自描述的,方便数据库的横向扩展。在MongoDBShard中,我们可以将一个Collection拆分成不同的Shard集群。这种拆分的方式变得非常简单,不需要JOIN操作。因为,DBA再也不用担心需要进行自夸节点的JOIN操作了。(关于MongoDB水平扩展的内容可以参考另一篇文章MongoDB水平扩展,你做对了吗?上面的embedded或者引用的是一种将一对多关系的两个表集成到一个Document中的方法,其实我们的数据表结构会复杂很多,一个企业级应用往往会设计成百上千张表,表与表之间会有一对一、一对多、多对多的关系.对于如此复杂的场景,我们目前还没有一个适用于所有情况的精确解决方案。基本上,需要对业务数据进行具体分析,推导出新的数据结构。在这里,我可以列举一些处理不同关系的基本原则和基本方法。基于这些基本的原则和方法,我想你总能根据自己的业务总结出一个行之有效的解决方案。具体到MongoDB,有两种方式联想嵌入和喝。我们来看看它们的应用场景。嵌入就像上面的例子,将关系数据中的表的一行嵌入到其关联表中,使其成为新Collection中的Document。这种嵌入方式适用于两种情况:当表关系是一对一的时候,或者当表关系是一对多的时候在以上两种关系中,如果关系表不经常单独查询,那么只依附于主表的查询,那么可以考虑使用内嵌的方式。以产品和产品价格为例。记录产品价格时,价格会随着时间的变化取不同的值。新推出的产品价格相对较高,随着时间的推移其价格会下降。双十一等节假日,价格也会临时调整。在分析产品销量的时候,我们还需要考虑产品在什么价位卖得最多,所以不能简单的把产品和价格放在一张表里,必须要有一个与产品关联的价目表,它有当前价格和历史价格记录产品的价格。那么我们在统计产品的销售报表的时候,这个价目表是不会单独存在的,它必须附在产品表上。此时,将产品价格嵌入到产品表中是一个比较可行的方案。查询语句可以通过一个Collection查询出所有商品相关的价格,从而避免了表之间的JOIN操作。但并不是所有的一对一和一对多关系都适合嵌套。嵌入式数据结构在以下情况需要谨慎使用:如果一个Document的大小超过了MongoDB的限制(16M),此时不应该考虑嵌入式数据结构。当你的数据表关系很复杂的时候,将所有相关数据嵌入到一个Document中可能会超过16M的限制。如果一个Document需要被频繁访问,而其中一个嵌入的Document很少被访问,则不适合使用embedding;因为这会增加MongoDB在检索数据时的内存消耗。如果一个Document中的一个内嵌Document需要经常修改,或者大小变化频繁,而另一个内嵌Document相对静态,则不要考虑使用内嵌结构。由于内嵌Document的增减会导致整个Document的大小发生变化,当变化超过分配给Document的磁盘空间时,会导致数据库为Document重新分配空间。引用除了嵌入之外,引用也可以用来关联数据。引用方式与关系数据库表的主键和外键非常相似。可以把主表和外键表存成一个Collection,然后用它们的_id来关联。_id是MongoDB文档中的一个特殊字段,由MongoDB自动生成,在一个Collection中唯一存在。但是,在使用引用时需要注意以下几点:在一些复杂的多对多关系表中,不要尝试引用,因为这样会增加应用程序逻辑的开发和维护。当使用嵌入式结构产生过多的重复数据时,可以考虑使用引用。虽然MongoDB不支持JOIN操作,但是可以通过Aggregation中的$lookup指令完成连接多张表的操作请求。应用集成定义好数据模型后,我们就可以开始应用集成了。集成方式可以使用MongoDBDriver,它支持几乎所有常用的计算机语言。易用性和开发效率高是MongoDB的两大特点。与SQL语句不同,MongoDB采用API的方式提供接口。开发者可以选择支持自己熟悉语言的Driver,DBA可以直接使用MongoShell脚本。幸运的是,MongoDB提供了一个API和SQL语句的对照表SQLtoMongoDBMappingChart供大家参考。另一个不能不提的强大功能是聚合框架(聚合)。并非所有NoSQL数据库都支持聚合。简单理解Aggregation可以看作是Hadoop中的MapReduce,或者SQL中的LeftJoin。在没有Aggregation的情况下,开发者需要进行如下的数据迁移操作:在应用层开发一个类似Aggregation的功能,将数据聚合在一起写入数据库。这样做增加了应用的复杂度,难以适应各种数据的组合。如果没有遇到新的需求,则需要一定的开发工作量。有人会把数据放到Hadoop里面,然后在上面运行MapReduce生成结果,再把结果倒入NoSQL中。这是一种折中的方式,但不支持实时数据迁移,只能离线操作。MongoDB支持原生聚合操作。您可以聚合需要迁移的数据。每个操作都可以想象成管道上的一个环节,所有的操作都可以连接起来形成一个聚合管道。Pipeline上的每个节点都有自己的输入输出,前一个节点的输出就是下一个节点的输入。有兴趣的同学可以在这个链接找到更多关于Aggregation操作的内容,里面列出了各个Aggregation命令和SQL语句的对应关系,SQL到AggregationMappingChart数据完整性关系数据库中,有很多支持ACID事务操作的方法和应用,DBA不希望在数据迁移过程中出现任何错误,例如数据完整性的丢失。MongoDB对此有不同形式的支持。在3.0以上版本,MongoDB支持WiredTigerStorageEngine,支持Document级别的锁操作。也就是说,在进行数据库写操作时,MongoDB可以保证Document操作的原子性,可以与其他操作完全分离。MongoDB除了支持对单个Dcoument的原子操作,还支持多Document事务。例如,findAndModify方法允许您在执行多个文档操作时保持事务完整。再比如,更新多个文档的原子操作可以通过PerformTwoPhaseCommits来实现。有关详细信息,请访问执行两阶段提交。数据一致性在数据一致性方面,MongoDB通过ReadPreference来调整一致性的程度。默认情况下,在一个MongoDBReplicaSet中,所有的数据库读操作都会发送到Primary服务器,ReplicaSet中的所有Secondaries保证数据的最终一致性。同时,MongoDB提供了修改这种一致性行为的方法。数据库管理员可以修改ReadPreference参数来满足不同的一致性要求。数据一致性可以有以下集中式解决方案:primary:在默认模式下,所有请求都会发送到primary。primaryPreferred:大部分读请求都会发送到Primary,但是当Primary无法访问时,请求会被转发到Secondary。secondary:所有请求都将发送到辅助。secondaryPreferred:大多数情况下,读请求都会发送到Secondary,但是如果Replica中没有Secondary,请求就会发送到Primary。nearest:请求将发送到离网络最近的服务器。这种模式在多个数据中心上工作得很好。数据迁移完成上面的设计和思考后,数据迁移就会变得更容易思考了。将数据导入MongoDB有几种不同的选择。您可以使用mongoimport导入JSON数据,也可以使用ETL(ExtractTransformLoad)工具。许多项目允许在当前应用程序运行的同时并行迁移关系数据库中的数据,并支持增量更新。具体操作如下:当从关系数据库中读取一条记录时,应用程序会将这条记录按照之前定义的JONS格式更新插入到MongoDB中。一致性校验,可以通过MD5等方法进行数据一致性校验。所有新的插入操作和数据修改操作都转移到MongoDB中完成。小结根据本文提供的方法和步骤,项目组可以减少数据迁移中不必要的时间和误操作。当然,数据永远是应用系统的核心内容。任何数据迁移都需要支持错误恢复,失败时必须能够快速恢复到之前的版本。在这方面,MongoDB实现了更加灵活的支持。有关详细信息,请参阅MongoDBWebnar。参考文献[DataModeling](https://docs.mongodb.com/manual/core/data-modeling-introduction/)[SQLtoMongoDBMappingChart](https://docs.mongodb.com/manual/reference/sql-comparison/)[SQL到聚合映射表](https://docs.mongodb.com/manual/reference/sql-aggregation-comparison/)[WiredTiger存储引擎](https://docs.mongodb.com/manual/core/wiredtiger/)[执行两阶段提交](https://docs.mongodb.com/manual/tutorial/perform-two-phase-commits/)