本教程向你介绍了MongoDB数据库。你将发现如何安装该软件、操作数据,并将数据设计技术应用于你自己的应用程序。
所有的例子都是使用MongoDB 5开发的,但大多数都可以在以前或以后的版本中使用。代码可以直接输入到客户端应用程序或MongoDB shell(mongo或mongosh),以查询和更新数据库
- 什么是MongoDB?
- MongoDB的要素
- 如何安装MongoDB
- 如何访问你的MongoDB数据库
- 如何在MongoDB中插入新文件
- 简单的MongoDB查询
- 在MongoDB中使用游标
- 如何在MongoDB中创建索引
- 如何管理MongoDB的索引
- 使用MongoDB的数据验证模式
- 如何更新MongoDB中的现有文件
- 如何删除MongoDB中的文件
- 在MongoDB中使用聚合操作
- 如何运行批量MongoDB操作
什么是MongoDB?
MongoDB是一个开源的NoSQL数据库。NoSQL意味着该数据库不像传统的SQL数据库那样使用关系表。
有一系列的NoSQL数据库类型,但MongoDB将数据存储在被称为文档的类似JavaScript的对象中,其内容看起来像这样:
{ _id: "123", name: "Craig" }
虽然MongoDB已经成为基于JavaScript的框架Node.js的代名词,但大多数框架、语言和运行时都有MongoDB官方数据库驱动,包括Node.js、PHP和Python。你也可以选择像Mongoose这样的库,提供更高层次的抽象或对象关系映射(ORM)功能。
与SQL表不同,你可以在MongoDB中存储的内容没有结构限制。数据模式不被强制执行。你可以在你喜欢的地方存储任何你喜欢的东西。这使得MongoDB成为更多有机的–或混乱的–数据结构的理想选择。
考虑一个联系地址簿。个人往往有多个电话号码。你可以在一个SQL表中定义三个电话字段,但这对一些联系人来说太多,对另一些人来说太少。最终,你将需要一个单独的电话表,这就增加了复杂性。
在MongoDB中,这些电话号码可以被定义为同一个文档中无限的对象阵列。
{ _id: "123", name: "Craig", telephone: [ { home: "0123456789" }, { work: "9876543210" }, { cell: "3141592654" } ] }
请注意,MongoDB使用类似的JavaScript对象符号进行数据更新和查询,如果你习惯于SQL,这可能会带来一些挑战。
MongoDB的要素
在我们进一步讨论之前,让我们看一下MongoDB的特点。我们将在本文中使用这些词汇。
- Document。数据存储中的一个单独的对象,类似于SQL数据库表中的记录或行。
- Field。文件中的一个单一数据项,如姓名或电话号码,类似于SQL字段或表列。
- Collection。一组类似的文件,类似于一个SQL表。虽然你可以把所有的文件放到一个集合中,但通常把它们分成特定的类型更实用。在一个联系人地址簿中,你可以有一个人的集合和一个公司的集合。
- Database。一个相关数据的集合,与SQL数据库的含义相同。模式。一个模式定义了数据
- Schema。在SQL数据库中,你必须在存储数据之前定义带有相关字段和类型的表定义。这在MongoDB中是没有必要的,尽管它可以创建一个模式,在文件被添加到集合之前对其进行验证。
- Index。一种用于提高查询性能的数据结构,与SQL索引的含义相同。
- Primary Key。每个文档的唯一标识符。MongoDB会自动为集合中的每个文档添加一个唯一的、有索引的_id字段。
- Denormalization。在SQL数据库中,”规范化 “是一种用于组织数据和消除重复的技术。在MongoDB中,鼓励 “去规范化”。你积极地重复数据,一个单一的文件可以包含所有需要的信息。
- JOINs。SQL提供了一个JOIN操作符,因此可以在一次查询中从多个规范化的表中检索数据。在MongoDB中,直到3.6版本才可以进行连接,而且限制仍然存在。这也是为什么数据应该被反规范化为自包含文件的另一个原因。
- Transactions。当一个更新改变了一个文档上的两个或更多的值时,MongoDB确保它们全部成
功或全部失败。跨越两个或多个文档的更新必须被包裹在一个事务中。MongoDB从4.0
版本开始支持交易,但需要一个多服务器副本集或分片集群。下面的安装示例使用的是
单台服务器,因此不可能有事务。
如何安装MongoDB
在你的本地机器上使用MongoDB,你有三种选择。我们将引导你了解每个选项。
1. 使用Docker(推荐)。
Docker是一个软件管理工具,可以在几分钟内安装、配置和运行MongoDB或任何其他应用程序。
安装Docker和Docker Compose,然后创建一个项目文件夹,其中有一个名为dockercompose.yml的文件,包含以下内容(注意,缩略号是必不可少的)。
version: '3' services: mongodb: image: mongo:5 environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=pass - MONGO_INITDB_DATABASE=mongodemo container_name: mongodb volumes: - dbdata:/data/db ports: - "27017:27017" adminer: image: dehy/adminer container_name: adminer depends_on: - mongodb ports: - "8080:80" volumes: dbdata:
从命令行访问该文件夹并运行。
docker-compose up
最新版本的MongoDB 5将被下载并启动。这在第一次启动时需要几分钟的时间,但随后的运行会快很多。
请注意:
- 一个MongoDB管理员账户被定义为ID “root “和密码 “pass”。
- 数据在重启之间被保存在一个名为dbdata的Docker卷中。
- 还提供了Adminer数据库客户端。
您可以使用任何MongoDB数据库客户端,使用ID “root “和密码 “pass “连接到localhost:27017。或者,你可以访问Adminer,网址是http://localhost:8080/,用以下凭证登录。
- System: MongoDB (alpha)
- Server: host.docker.internal
- Username: root
- Password: pass
信息
服务器host.docker.internal可以在运行Docker Desktop的Mac和Windows设备上工作。Linux用户应该使用设备的网络IP地址,而不是localhost(Adminer将其解析为自己的Docker容器)。
Adminer登陆
Adminer允许你检查集合和文件。然而,要注意的是,集合被称为 “表”。
Adminer集合视图
要运行命令,你可以使用MongoDB Shell (mongosh
) 或传统的 mongo
命令行REPL(读取评估打印循环)环境。
访问Docker MongoDB容器的bash shell。
docker exec -it mongodb bash
然后用ID和密码启动MongoDB shell:
mongosh -u root -p pass
(如果你愿意,可以使用传统的 mongo
命令。)然后你可以发布
MongoDB的命令,比如下面这些。
show dbs;
— – 显示所有数据库use mongodemo;
— 使用一个特定的数据库show collections;
— 列出数据库中的集合db.person.find();
— 列出一个集合中的所有文档exit;
— 退出/关闭shell
通过在项目目录下运行以下命令关闭MongoDB:
docker-compose down
2. 使用云供应商(无需安装)。
你可以使用一个托管的MongoDB实例,所以不需要在本地安装任何东西。互联网连接是必不可少的,响应速度将取决于主机和你的带宽。大多数服务将收取每月和/或兆字节的使用费。
主机通常会提供详细信息,因此你可以使用MongoDB客户端软件远程管理数据库
3. 在本地安装MongoDB
MongoDB可以在Linux、Windows或Mac OS上安装和配置。有两个版本可供选择:
- 一个商业的企业版
- 一个开源的社区版(在本教程中使用)。
MongoDB的安装页面提供了各种操作系统的说明。一般来说:
- Linux版本使用软件包管理器进行安装,如Ubuntu上的apt
- Mac OS版本使用brew进行安装。
- Windows版本使用.msi安装程序进行安装
请务必仔细按照说明进行操作,这样你的安装才会成功!
如何访问你的MongoDB数据库
现在你的MongoDB数据库已经安装完毕,现在是时候学习如何管理它了。让我们讨论一下,为了访问和使用你的数据库,你需要做什么。
1. 安装一个MongoDB客户端
管理数据库需要一个MongoDB客户端应用程序。如果你使用的是云端或本地安装,我们建议你安装命令行mongosh MongoDB Shell。
Adminer是一个基于Web的数据库客户端,它支持MongoDB,尽管目前它仅限于检查集合。Adminer可作为一个单一的PHP脚本下载,但如果你使用Docker安装,它就已经设置好了。
GUI客户端应用程序为更新和检查数据提供了一个更好的界面。有几种选择,包括免费和跨平台的MongoDB Compass:
MongoDB Compass
Studio 3T,另一个GUI竞争者,提供了一个商业应用程序,免费授予有限的功能。
Studio 3T
你可以通过使用以下任何一种工具来访问你的MongoDB数据库。
- 机器网络名称、URL或IP地址(本地安装的是localhost)。
- MongoDB的端口(默认为27017)。
- 一个用户ID和一个password。一个根用户通常是在安装时定义的。
2. 设置和保存数据库访问凭证
根管理员对所有数据库都有不受限制的访问权。一般来说,你应该使用一个具有特定权限的自定义用户来限制访问并提高安全性。
例如,以下命令创建了一个名为myuser的用户,其密码为mypass,他对mydb数据库有读和写的权限。
use mydb; db.createUser({ user: "myuser", pwd: "mypass", roles: [ { role: "readWrite", db: "mydb" } ] });
如何在MongoDB中插入新文件
在插入你的第一个文档之前,不需要定义一个数据库或集合。使用任何MongoDB客户端,简单地切换到一个名为mongodemo的数据库:
use mongodemo;
然后在一个新的人员集合中插入一个单一的文件:
db.person.insertOne( { name: 'Abdul', company: 'Alpha Inc', telephone: [ { home: '0123456789' }, { work: '9876543210' } ] } );
通过运行查询来查看文件,以返回人物集合的所有结果:
db.person.find({});
其结果将是这样的:
{ "_id" : ObjectId("62442429854636a03f6b8534"), name: 'Abdul', company: 'Alpha Inc', telephone: [ { home: '0123456789' }, { work: '9876543210' } ] }
如何插入多个文件
你可以通过向insertMany()传递一个数组来向一个集合中插入多个文档。下面的代码创建了额外的人物文档和一个新的公司集合:
db.person.insertMany([ { name: 'Brian', company: 'Beta Inc' }, { name: 'Claire', company: 'Gamma Inc', telephone: [ { cell: '3141592654' } ] }, { name: 'Dawn', company: 'Alpha Inc' }, { name: 'Esther', company: 'Beta Inc', telephone: [ { home: '001122334455' } ] }, { name: 'George', company: 'Gamma Inc' }, { name: 'Henry', company: 'Alpha Inc', telephone: [ { work: '012301230123' }, { cell: '161803398875' } ] }, ]); db.company.insertMany([ { name: 'Alpha Inc', base: 'US' }, { name: 'Beta Inc', base: 'US' }, { name: 'Gamma Inc', base: 'GB' }, ]);
_id从何而来?
MongoDB自动为集合中的每个文档分配一个_id。这是一个ObjectID- 一个BSON(二进制Javascript对象符号)值,其中包含:
- 创建时的Unix epoch(秒)(4个字节)
- Aa 5个字节的机器/进程ID
- 一个3字节的计数器,从一个随机值开始
这是该文档的主键。这个24个字符的十六进制值在数据库中的所有文档中保证是唯一的,而且一旦插入就不能改变。
MongoDB还提供了一个getTimeStamp()函数,因此你可以获得文档的创建日期/时间,而不需要明确设置一个值。另外,你可以在创建文档时定义你自己独特的_id值。
数据去规范化
上面插入的记录将每个用户的公司设置为一个字符串,如 “Alpha Inc”。在规范化的SQL数据库中不建议这样做:
- 这很容易出错:一个用户被分配到 “阿尔法公司”,而另一个则是 “阿尔法公司”(附加句号字符)。他们被视为不同的公司。
- 更新一个公司名称可能意味着更新许多记录。
SQL的解决方案是创建一个公司表,并使用其主键(可能是一个整数)将一个公司与一个人联系起来。无论公司名称如何变化,主键都会保持不变,数据库可以执行规则以保证数据的完整性。
在MongoDB中鼓励去规范化。你应该积极地重复数据,一个单一的文件可以包含它所需要的所有信息。这有几个优点:
- 文件是自成一体的,更容易阅读–不需要参考其他文集。
- 写入性能可以比SQL数据库快,因为执行的数据完整性规则较少。
- 分片–或将数据分布在多台机器上–变得更容易,因为不需要引用其他集合中的数据。
简单的MongoDB查询
你可以通过使用一个空的find()来列出一个集合中的所有文档,比如person。
db.person.find({})
count()方法返回文档的数量(在我们的例子中,这个数字将是7)。
db.person.find({}).count();
sort()方法按你喜欢的任何顺序返回文件,如按姓名的反字母顺序。
db.person.find({}).sort({ name: -1 });
你也可以限制返回的文件数量,例如,找到前三个名字:
db.person.find({}).sort({ name: 1 }).limit(2);
你可以通过定义一个或多个字段的查询来搜索特定的记录,例如,定位所有名字被设置为 “Claire”的人的文件。
db.person.find({ name: 'Claire' });
也支持逻辑运算符,如$and、$or、$not、$gt(大于)、$lt(小于)和$ne(不等于),例如,定位所有公司为 “Alpha Inc “或 “Beta Inc “的个人文件。
db.person.find({ $or: [ { company: 'Alpha Inc' }, { company: 'Beta Inc' } ] });
在这个例子的数据库中,同样的结果可以用$nin(不在)来提取所有公司不是 “Gamma Inc “的文档。
db.person.find({ company: { $nin: ['Gamma Inc'] } });
find()方法中的第二个值对象设置了一个投影,该投影定义了返回的字段。在这个例子中,只有名字被返回(注意,除非明确关闭,否则文档_id总是被返回)。
db.person.find( { name:'Claire' }, { _id:0, name:1 } );
其结果是:
{ "name" : "Claire" }
通过$elemMatch查询,可以找到一个数组中的项目,比如电话数组中有工作项目的所有文件。同样的$elemMatch可以在投影中使用,只显示工作编号:
db.person.find( { telephone: { $elemMatch: { work: { $exists: true }} } }, { _id: 0, name:1, telephone: { $elemMatch: { work: { $exists: true }}} } );
其结果是:
{ "name" : "Abdul", "telephone" : [ { "work" : "9876543210" } ] }, { "name" : "Henry", "telephone" : [ { "work" : "012301230123" } ] }
在MongoDB中使用游标
大多数数据库驱动程序允许将查询结果以数组或类似的数据结构形式返回。然而,如果该集合包含成千上万的文件,这可能导致内存问题。
像大多数SQL数据库一样,MongoDB支持游标的概念。游标允许应用程序在进入下一个项目或放弃搜索之前一次读取查询结果。
游标也可以从MongoDB shell中使用。
let myCursor = db.person.find( {} ); while ( myCursor.hasNext() ) { print(tojson( myCursor.next() )); }
如何在MongoDB中创建索引
这个人名集合目前有7个文件,所以任何查询都不会有计算成本。然而,想象一下你有一百万个有姓名和电子邮件地址的联系人。联系人可能是按姓名排序的,但电子邮件地址会在一个似乎是随机顺序。
如果你需要通过电子邮件查找一个联系人,数据库将不得不搜索多达一百万个项目才能找到匹配。在电子邮件字段上添加索引可以创建一个查找 “表”,其中电子邮件按字母顺序存储。数据库现在可以使用更有效的搜索算法来定位正确的人。
随着文档数量的增加,索引变得至关重要。一般来说,你应该对任何可能在查询中被引用的字段应用一个索引。你可以对每个字段都应用索引,但要注意这将减慢数据更新的速度,并增加所需的磁盘空间,因为重新编制索引成为必要。
MongoDB提供了一系列的索引类型。
单一字段索引
大多数索引将应用于单个字段,例如,以升序字母顺序对姓名字段进行索引:
db.person.createIndex({ name: 1 });
使用-1会颠倒顺序。这在我们的例子中没有什么好处,但如果你有一个日期字段,最近的事件优先,这可能很实用。
在例子mongodemo数据库中,还有三个索引是有用的:
db.person.createIndex( { company: 1 } ); db.company.createIndex( { name: 1 } ); db.company.createIndex( { base: 1 } );
多个字段的复合索引
在一个索引中可以指定两个或多个字段,例如
db.person.createIndex( { name: 1, company: 1 } );
当一个字段在搜索查询中经常与另一个字段一起使用时,这可能是有用的。
阵列或对象元素的多键索引
文件可能很复杂,往往需要对结构中更深的字段进行索引,如工作电话:
db.products.createIndex( { 'telephone.work': 1 } );
通配符索引
通配符可以对文档中的每一个字段进行索引。这在较小和较简单的文件上通常是实用的,这些文件可以用各种方式进行查询:
db.company.createIndex( { '$**': 1 } );
全文索引
文本索引允许你创建类似搜索引擎的查询,可以检查所有字符串字段的文本并按相关性排序。你可以将文本索引限制在特定字段:
db.person.createIndex( { name: "text", company: "text" } );
…或者在所有字符串字段上创建一个文本索引:
db.person.createIndex( { "$**": "text" } );
$text运算符允许你搜索这个索引,比如找到所有引用 “Gamma “的文件:
db.person.find({ $text: { $search: 'Gamma' } });
请注意,全文搜索一般需要五个或更多的字符才能返回有用的结果。
其他指数类型
MongoDB提供了其他几种专门的索引类型:
- hashed index
- 2d index — 二维平面上的点
- 2dsphere index — 类地球体上的几何图形
如何管理MongoDB的索引
在一个集合上定义的索引可以用以下方法检查:
db.person.getIndexes();
这将返回一个数组的结果,如:
[ { "v" : 2.0, "key" : { "_id" : 1.0 }, "name" : "_id_" }, { "v" : 2.0, "key" : { "company" : 1.0 }, "name" : "company_1" }, { "v" : 2.0, "key" : { "name" : 1.0 }, "name" : "name_1" } ]
key “定义了字段和顺序,而 “name “是该索引的唯一标识符–例如 “company_1 “是公司字段的索引。
索引的有效性可以通过给任何查询添加一个.explain() 方法来检查,例如:
db.person.find({ name:'Claire' }).explain();
这将返回一个大的数据集,但 “winningPlan “对象显示了查询中使用的 “indexName”:
"winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "name" : 1.0 }, "indexName" : "name_1", } }
如果有必要,你可以通过引用一个索引的名称来放弃它:
db.person.dropIndex( 'name_1' );
或使用索引规范文件:
db.person.dropIndex({ name: 1 });
.dropIndexes()方法允许你在一条命令中放弃一个以上的索引。
使用MongoDB的数据验证模式
与SQL不同,数据定义模式在MongoDB中是不需要的。你可以在任何时候向任何集合中的任何文档发布任何数据。
这提供了相当大的自由。然而,有时你可能要坚持遵守规则。例如,除非一个文件包含一个名字,否则就不可能将其插入到人的集合中。
验证规则可以使用$jsonSchema对象来指定,该对象定义了一个所需项目的数组和每个验证字段的属性。人的集合已经被创建,但是你仍然可以定义一个模式,指定一个名字字符串是必须的:
db.runCommand({ collMod: 'person', validator: { $jsonSchema: { required: [ 'name' ], properties: { name: { bsonType: 'string', description: 'name string required' } } } } });
试着插入一个没有名字的人的文件:
db.person.insertOne({ company: 'Alpha Inc' });
…命令就会失败:
{ "index" : 0.0, "code" : 121.0, "errmsg" : "Document failed validation", "op" : { "_id" : ObjectId("624591771658cd08f8290401"), "company" : "Alpha Inc" } }
如果你在使用一个集合之前创建了该集合,也可以定义模式。下面的命令实现了与上述相同的规则。
db.createCollection('person', { validator: { $jsonSchema: { required: [ 'name' ], properties: { name: { bsonType: 'string', description: 'name string required' } } } } });
这个更复杂的例子创建了一个用户集合,验证了必须提供姓名、电子邮件地址和至少一个电话号码:
db.createCollection('users', { validator: { $jsonSchema: { required: [ 'name', 'email', 'telephone' ], properties: { name: { bsonType: 'string', description: 'name string required' }, email: { bsonType: 'string', pattern: '^.+\@.+$', description: 'valid email required' }, telephone: { bsonType: 'array', minItems: 1, description: 'at least one telephone number required' } } } } });
如何更新MongoDB中的现有文件
MongoDB提供了几种更新方法,包括 updateOne()
, updateMany()
和replaceOne()
。这些都是通过。
- 一个过滤器对象,用于定位要更新的文件
- 一个更新对象–或一个更新对象的数组–描述要改变的数据 一个可选的选项对象。
- 最有用的属性是upsert,它可以在没有发现的情况下插入一个新的文档。
下面的例子更新了姓名被设置为 “Henry “的人的文件。它删除了工作电话,增加了家庭电话,并设置了一个新的出生日期。
db.person.updateOne( { name: 'Henry' }, [ { $unset: [ 'telephone.work' ] }, { $set: { 'birthdate': new ISODate('1980-01-01'), 'telephone': [ { 'home': '789789789' } ] } } ] );
接下来的例子更新了姓名被设置为 “Ian “的人名文档。这个名字目前并不存在,但将upsert设置为 “true “就可以创建它。
db.person.updateOne( { name: 'Ian' }, { $set: { company: 'Beta Inc' } }, { upsert: true } );
你可以在任何时候运行查询命令来检查数据更新。
如何删除MongoDB中的文件
上面的更新例子使用$unset从名字为 “Henry “的文档中删除工作电话。要删除整个文档,你可以使用几个删除方法中的一个,包括deleteOne()
, deleteMany()
和remove()
(可以删除一个或多个)。
为Ian新创建的文件可以用适当的过滤器删除。
db.person.deleteOne({ name: 'Ian' });
在MongoDB中使用聚合操作
聚合是强大的,但可能难以理解。它定义了一个系列–或管道- 在一个数组中的操作。该管道的每个阶段都会执行一个操作,如过滤、分组、计算或修改一组文档。一个阶段也可以使用类似SQL JOIN的$lookup操作。得到的文件被传递给管道的下一个阶段,以便在必要时进行进一步处理。
聚合最好用一个例子来说明。我们将逐步建立一个查询,返回在美国某机构工作的人的姓名、公司和工作电话(如果有的话)。
第一个操作是运行一个$match来过滤美国的公司。
db.company.aggregate([ { $match: { base: 'US' } } ]);
这样的返回:
{ "_id" : ObjectId("62442429854636a03f6b853b"), "name" : "Alpha Inc", "base" : "US" } { "_id" : ObjectId("62442429854636a03f6b853c"), "name" : "Beta Inc", "base" : "US" }
然后我们可以添加一个新的$lookup管道操作符,将公司名称(localField)与人(from)集合中的公司(foreignField)进行匹配。输出结果将作为一个雇员数组附加到每个公司的文件中:
db.company.aggregate([ { $match: { base: 'US' } }, { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } } ]);
结果是这样的:
{ "_id" : ObjectId("62442429854636a03f6b853b"), "name" : "Alpha Inc", "base" : "US", "employee" : [ { "_id" : ObjectId("62442429854636a03f6b8534"), "name" : "Abdul", "company" : "Alpha Inc", "telephone" : [ { "home" : "0123456789" }, { "work" : "9876543210" } ] }, { "_id" : ObjectId("62442429854636a03f6b8537"), "name" : "Dawn", "company" : "Alpha Inc" }, { "_id" : ObjectId("62442429854636a03f6b853a"), "name" : "Henry", "company" : "Alpha Inc", "telephone" : [ { "home" : "789789789" } ], } ] } { "_id" : ObjectId("62442429854636a03f6b853c"), "name" : "Beta Inc", "base" : "US", "employee" : [ { "_id" : ObjectId("62442429854636a03f6b8535"), "name" : "Brian", "company" : "Beta Inc" }, { "_id" : ObjectId("62442429854636a03f6b8538"), "name" : "Esther", "company" : "Beta Inc", "telephone" : [ { "home" : "001122334455" } ] } ] }
现在,一个$project(投射)操作可以删除所有的,但雇员数组除外。随后的$unwind操作可以破坏数组,获得独立的雇员文件。
db.company.aggregate([ { $match: { base: 'US' } }, { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } }, { $project: { _id: 0, employee: 1 } }, { $unwind: '$employee' } ]);
其结果是:
{ "employee" : { "_id" : ObjectId("62442429854636a03f6b8534"), "name" : "Abdul", "company" : "Alpha Inc", "telephone" : [ { "home" : "0123456789" }, { "work" : "9876543210" } ] } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b8537"), "name" : "Dawn", "company" : "Alpha Inc" } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b853a"), "name" : "Henry", "company" : "Alpha Inc", "telephone" : [ { "home" : "789789789" } ] } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b8535"), "name" : "Brian", "company" : "Beta Inc" } } { "employee" : { "_id" : ObjectId("62442429854636a03f6b8538"), "name" : "Esther", "company" : "Beta Inc", "telephone" : [ { "home" : "001122334455" } ] } }
最后,$replaceRoot操作被用来格式化每个文件,所以只返回个人的名字、公司和工作电话。接下来是一个$sort操作,以升序的名字顺序输出文件。完整的聚合查询:
db.company.aggregate([ { $match: { base: 'US' } }, { $lookup: { from: 'person', localField: 'name', foreignField: 'company', as: 'employee' } }, { $project: { _id: 0, employee: 1 } }, { $unwind: '$employee' }, { $replaceRoot: { newRoot: { $mergeObjects: [ { name: "$employee.name", company: '$employee.company', work: { $first: '$employee.telephone.work' } }, "$name" ] } } }, { $sort: { name: 1 } } ]);
其结果是:
{ "name" : "Abdul", "company" : "Alpha Inc", "work" : "9876543210" } { "name" : "Brian", "company" : "Beta Inc", } { "name" : "Dawn", "company" : "Alpha Inc", } { "name" : "Esther", "company" : "Beta Inc" } { "name" : "Henry", "company" : "Alpha Inc" }
还有其他方法可以实现这一结果,但关键是MongoDB可以完成大部分的工作。很少有必要在你的应用程序代码中直接读取文档和操作数据。
如何运行批量MongoDB操作
默认情况下,MongoDB可以处理1,000个并发操作。在使用mongosh时,这不太可能是一个问题,但如果应用程序对单个记录进行一系列的数据操作,就会达到这个限制。Node.js应用程序尤其有问题,因为它们可以迅速发出一系列异步请求,而不必等到它们完成。
为了规避这个问题,MongoDB提供了一个批量操作API,它接受任何数量的更新,这些更新可以按顺序或按任何顺序执行。
这里有一个Node.js的伪代码例子。
// reference the mycollection collection const bulk = db.collection('mycollection').initializeUnorderedBulkOp(); // make any number of data changes bulk.insertOne(...); bulk.insertMany(...) bulk.updateOne(...); bulk.deleteOne(...); // etc... bulk.execute();
最后一条语句有效地发出了一个MongoDB请求,所以你有较少的机会达到1000个操作的限制。
小结
MongoDB为内容管理系统、地址簿和社交网络等应用提供了一个灵活的存储,在这些应用中,严格的数据结构过于僵硬,难以定义。数据写入速度快,在多个服务器上分片变得更容易。
使用MongoDB数据库编写应用程序也可以是一种解放。它可以在任何时候将任何数据存储在任何文档的集合中。当你使用敏捷方法学开发一个原型或最小可行产品时,这一点尤其实用,因为需求会随着时间的推移而变化。
也就是说,复杂的查询可能是一个挑战,当你从SQL世界迁移过来的时候,反规范化的概念很难接受。
MongoDB不太适合有严格交易要求的应用程序,因为数据的完整性是至关重要的,例如银行、会计和库存控制系统。这些有可识别的数据字段,应在开始编码前设计好。
在这两个极端之间有很多应用类型,所以选择一个合适的数据库变得更加困难。幸运的是,包括MongoDB在内的NoSQL数据库已经开始采用类似SQL的选项,包括JOIN和事务。
相反,SQL数据库,如MySQL和PostgreSQL现在提供类似NoSQL的JSON数据字段。他们也可能值得你注意,但像往常一样,最终的选择权在你手中。
评论留言