mongo-02-创建文档和ObjectId

MongoDB 是一个面向文档(document-oriented)的数据库,而不是关系型数据库。不采用关系模型主要是为了获得更好的扩展性。

与关系型数据库相比,面向文档的数据库不再有“行”(row)的概念,取而代之的是更为灵活的“文档”(document)模型。通过在文档中嵌入文档和数组,面向文档的方法能够仅使用一条记录来表现复杂的层次关系,另外,不再有预定义模式(predefined schema):文档的键(key)和值(value)不再是固定的类型和大小。由于没有固定的模式,根据需要添加或删除字段变得更容易了。

通常,由于开发者能够进行快速迭代,所以开发进程得以加快。而且,实验更容易进行。开发者能尝试大量的数据模型,从中选择一个最好的。

1. 连接Mongo服务

搭建好mongo服务后(可参考上一篇文章),可以使用mongo安装目录的bin目录下的 mongo 工具连接mongo服务。
mongo --host [host] --port [port]
host 不指定则默认是localhost;
port 不指定则默认是 27017

例如:

[root@docker01 ~]# /usr/local/mongodb-4.4.3/bin/mongo --host localhost --port 27017
MongoDB shell version v4.4.3
connecting to: mongodb://localhost:27017/?compressors=disabled&gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("0a47ded5-7908-45f7-97e4-e40876737bf1") }
MongoDB server version: 4.4.3
---
The server generated these startup warnings when booting: 
        2021-01-13T23:08:55.449+08:00: Access control is not enabled for the database. Read and write access to data and configuration is unrestricted
        2021-01-13T23:08:55.449+08:00: You are running this process as the root user, which is not recommended
        2021-01-13T23:08:55.449+08:00: /sys/kernel/mm/transparent_hugepage/enabled is 'always'. We suggest setting it to 'never'
        2021-01-13T23:08:55.449+08:00: /sys/kernel/mm/transparent_hugepage/defrag is 'always'. We suggest setting it to 'never'
        2021-01-13T23:08:55.449+08:00: Soft rlimits too low
        2021-01-13T23:08:55.449+08:00:         currentValue: 1024
        2021-01-13T23:08:55.449+08:00:         recommendedMinimum: 64000
---
---
        Enable MongoDB's free cloud-based monitoring service, which will then receive and display
        metrics about your deployment (disk utilization, CPU, operation statistics, etc).

        The monitoring data will be available on a MongoDB website with a unique URL accessible to you
        and anyone you share the URL with. MongoDB may use this information to make product
        improvements and to suggest MongoDB products and deployment options to you.

        To enable free monitoring, run the following command: db.enableFreeMonitoring()
        To permanently disable this reminder, run the following command: db.disableFreeMonitoring()
---
> 

2. 切换、查看数据库、查看所有集合

use [database] :切换数据库
show databases :查看所有数据库
show collections :查看数据库中的所有 collection

若还不存在该数据库,use操作并不会主动去创建数据库。
在创建 document 时,如果 database 不存在则会创建 database,如果 collection 不存在,则会去创建 collection 。

# 切换到test数据库,这里并不会创建数据库
> use test
switched to db test
# 可看到test数据库并不存在
> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
# test还没有collection
> show collections
> 

3. 创建文档(document)

3.1 insertOne 创建一个 document

db.<collection>.insertOne( <document>, { writeConcern: <document> } )

writeConcern:
这个字段指定的文档定义了本次文档创建操作的安全写级别,简单来说,安全写级别用来判断写操作是否成功,安全写级别越高,丢失数据的风险越低,而写入操作的延迟也越高。
如果不提供 writeConcern 文档,mongoDB使用默认的安全写级别。

下面,向 user 集合中写入一条文档,由于之前还不存在 test 数据库和 user 集合,则会先创建数据库和集合,然后插入文档。

# 插入单条文档
> db.user.insertOne({"_id":"1","money":1000,"name":"刘一"})
{ "acknowledged" : true, "insertedId" : "1" }
# 自动建库
> show databases
admin   0.000GB
config  0.000GB
local   0.000GB
test    0.000GB
# 自动建集合
> show collections
user
# 查看插入的文档,find操作下面会介绍
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
> 

在上面插入文档的 insertOne 操作中可看到返回结果:
{ "acknowledged" : true, "insertedId" : "1" }

  • acknowledged: true表示安全写级别被启用,我们并没有提供 writeConcern 文档,这里mongoDB使用默认的安全写级别。
  • insertedId: 被写入的文档的 _id 。

3.2 insertMany 创建多个 document

db.<collection>.insertMany( [<document1>,<document2>,<document3>...], { writeConcern: <document>, ordered: <boolean> } )

  • ordered: 指定是否要按文档数组的顺序写入MongoDB,如果为false,这些文档则会无序写入,可以提升写入性能,ordered的默认值是 true。

提示:

  • ordered = true 顺序写入,一旦遇到错误,操作便会退出,剩余的文档无论是否正确都不会被写入。
  • ordered = false 无序写入,即使某些文档发生了错误,剩余的正确文档仍然会被写入数据库。

将多个需要创建的文档作为一个数组传入 db.<collection>.insertMany()

3.2.1 ordered为true,默认就是true

一旦遇到错误,操作便会退出,剩余的文档无论是否正确都不会被写入,下面第一条文档与已有的文档 _id 重复,第3条文档与第2条文档的 _id 也重复,抛出 duplicate key error

> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
> db.user.insertMany([{"_id":"1","money":1000,"name":"刘能"},{"_id":"2","money":1000,"name":"陈二"},{"_id":"2","money":1000,"name":"二狗子"},{"_id":"3","money":1000,"name":"张三"}])
# 第一条文档抛出重复id错误,后面所有文档就算正确,也不执行插入操作
uncaught exception: BulkWriteError({
	"writeErrors" : [
		{
			"index" : 0,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: \"1\" }",
			"op" : {
				"_id" : "1",
				"money" : 1000,
				"name" : "刘能"
			}
		}
	],
	"writeConcernErrors" : [ ],
	"nInserted" : 0,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
}) :
BulkWriteError({
	"writeErrors" : [
		{
			"index" : 0,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: \"1\" }",
			"op" : {
				"_id" : "1",
				"money" : 1000,
				"name" : "刘能"
			}
		}
	],
	"writeConcernErrors" : [ ],
	"nInserted" : 0,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})
BulkWriteError@src/mongo/shell/bulk_api.js:367:48
BulkWriteResult/this.toError@src/mongo/shell/bulk_api.js:332:24
Bulk/this.execute@src/mongo/shell/bulk_api.js:1186:23
DBCollection.prototype.insertMany@src/mongo/shell/crud_api.js:326:5
@(shell):1:1
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
> 

3.2.2 order为false

无序写入,即使某些文档发生了错误,剩余的正确文档仍然会被写入数据库

> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
> db.user.insertMany([{"_id":"1","money":1000,"name":"刘能"},{"_id":"2","money":1000,"name":"陈二"},{"_id":"2","money":1000,"name":"二狗子"},{"_id":"3","money":1000,"name":"张三"}],{ordered: false})
uncaught exception: BulkWriteError({
	"writeErrors" : [
		{
			"index" : 0,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: \"1\" }",
			"op" : {
				"_id" : "1",
				"money" : 1000,
				"name" : "刘能"
			}
		},
		{
			"index" : 2,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: \"2\" }",
			"op" : {
				"_id" : "2",
				"money" : 1000,
				"name" : "二狗子"
			}
		}
	],
	"writeConcernErrors" : [ ],
	"nInserted" : 2,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
}) :
BulkWriteError({
	"writeErrors" : [
		{
			"index" : 0,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: \"1\" }",
			"op" : {
				"_id" : "1",
				"money" : 1000,
				"name" : "刘能"
			}
		},
		{
			"index" : 2,
			"code" : 11000,
			"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: \"2\" }",
			"op" : {
				"_id" : "2",
				"money" : 1000,
				"name" : "二狗子"
			}
		}
	],
	"writeConcernErrors" : [ ],
	"nInserted" : 2,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})
BulkWriteError@src/mongo/shell/bulk_api.js:367:48
BulkWriteResult/this.toError@src/mongo/shell/bulk_api.js:332:24
Bulk/this.execute@src/mongo/shell/bulk_api.js:1186:23
DBCollection.prototype.insertMany@src/mongo/shell/crud_api.js:326:5
@(shell):1:1
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
{ "_id" : "2", "money" : 1000, "name" : "陈二" }
{ "_id" : "3", "money" : 1000, "name" : "张三" }
> 

3.3 insert 创建单个或者多个文档

db.<collection>.insertMany( <document or array of documents>, { writeConcern: <document>, ordered: <boolean> } )

创建单个文档

> db.user.insert({"_id":"4","money":1000,"name":"李四"})
WriteResult({ "nInserted" : 1 })
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
{ "_id" : "2", "money" : 1000, "name" : "陈二" }
{ "_id" : "3", "money" : 1000, "name" : "张三" }
{ "_id" : "4", "money" : 1000, "name" : "李四" }
> 

创建多个文档

> db.user.insert([{"_id":"5","money":1000,"name":"王五"},{"_id":"6","money":1000,"name":"赵六"}])
BulkWriteResult({
	"writeErrors" : [ ],
	"writeConcernErrors" : [ ],
	"nInserted" : 2,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
{ "_id" : "2", "money" : 1000, "name" : "陈二" }
{ "_id" : "3", "money" : 1000, "name" : "张三" }
{ "_id" : "4", "money" : 1000, "name" : "李四" }
{ "_id" : "5", "money" : 1000, "name" : "王五" }
{ "_id" : "6", "money" : 1000, "name" : "赵六" }
> 

3.4 insertOne,insertMany和 insert 的区别

insertOne 和 insertMany 不支持 db.collection.explain() 命令。
insert 支持 db.collection.explain() 命令。

3.5 save 也可以创建文档

db.<collection>.save( <document>, { writeConcern: <document>, } )

当 db.collection.save() 命令处理一个新文档的时候,它会调用 db.collection.insert() 命令,所以 db.collection.save() 返回的结果文档与 db.collection.insert() 是一样的。

使用 save 创建单个文档

> db.user.save({"_id":"7","money":1000,"name":"孙七"})
WriteResult({ "nMatched" : 0, "nUpserted" : 1, "nModified" : 0, "_id" : "7" })
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
{ "_id" : "2", "money" : 1000, "name" : "陈二" }
{ "_id" : "3", "money" : 1000, "name" : "张三" }
{ "_id" : "4", "money" : 1000, "name" : "李四" }
{ "_id" : "5", "money" : 1000, "name" : "王五" }
{ "_id" : "6", "money" : 1000, "name" : "赵六" }
{ "_id" : "7", "money" : 1000, "name" : "孙七" }
> 

4. 主键 _id 和ObjectId

MongoDB 中存储的文档 必须 有一个 “_id” 键。这个键的值可以是任何类型的,默认是个 ObjectId 对象。

在一个集合里面,每个文档都有唯一的 “_id”,确保集合里面每个文档都能被唯一标识。如果有两个集合的话,两个集合可以都有一个 “_id” 的值为 123,但是每个集合里面只能有一个文档的 “_id” 值为123。

4.1 ObjectId 可以做分布式id

ObjectId 是 “_id” 的默认类型。它设计成轻量型的,不同的机器都能用全局唯一的同种方法方便地生成它。在多个服务器上同步自动增加主键值既费力又费时,因为设计 MongoDB 的初衷就是用作分布式数据库,所以能够在分布式环境中生成唯一的 “_id” 非常重要。

ObjectId 使用 12 字节的存储空间,是一个由 24 个十六进制数字组成的字符串(每个字节可以存储两个十六进制数字)。

ObjectId 的 12 字节按照如下方式生成:

mongo-02-创建文档和ObjectId

0 1 2 3 前 4 个字节时间戳
ObjectId 的 前 4 个字节 是从标准纪元开始的时间戳,单位为 。这会带来一些有
用的属性。

  • 时间戳,与随后的 5 字节(稍后介绍)组合起来,提供了秒级别的唯一性。
  • 由于时间戳在前,这意味着 ObjectId 大致会按照插入的顺序排列。这对于某些方面很有用,比如可以将其作为索引提高效率,但是这个是没有保证的,仅仅是“大致”。
  • 这 4 字节也隐含了文档创建的时间。绝大多数驱动程序都会提供一个方法,用于从 ObjectId 获取这些信息。因为使用的是当前时间,很多用户担心要对服务器进行时钟同步。在服务器间进行时间同步确实是个好主意,但是这里其实没有必要,因为时间戳的实际值并不重要,只要它总是不停增加就好了(每秒一次)。

接下来的3个字节机器主机名hash
接下来的3个字节是所在主机的唯一标识符。通常是机器主机名的散列值(hash)。这样就可以确保不同主机生成不同的 ObjectId,不产生冲突。

接下来的2个字节 PID
为了确保在同一台机器上并发的多个进程产生的 ObjectId 是唯一的,接下来的两字节来自产生 ObjectId 的进程的进程标识符(PID)。

最后 3 字节自动增加的计数器
前 9 字节保证了同一秒钟不同机器不同进程产生的 ObjectId 是唯一的。最后 3 字节是一个自动增加的计数器,确保相同进程同一秒产生的 ObjectId 也是不一样的。
一秒钟最多允许每个进程拥有 256 (1个字节8bit) 的三次方(16 777 216)个不同的 ObjectId。

如果插入的文档中,没有指定 _id,则默认会生成一个 ObjectId

> db.user.insertOne({"money":1000,"name":"周八"})
{
	"acknowledged" : true,
	"insertedId" : ObjectId("5fff6a78025e5d9ea86e18cc")
}
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
{ "_id" : "2", "money" : 1000, "name" : "陈二" }
{ "_id" : "3", "money" : 1000, "name" : "张三" }
{ "_id" : "4", "money" : 1000, "name" : "李四" }
{ "_id" : "5", "money" : 1000, "name" : "王五" }
{ "_id" : "6", "money" : 1000, "name" : "赵六" }
{ "_id" : "7", "money" : 1000, "name" : "孙七" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八" }
> 

4.2 复合主键

可以使用文档作为文档主键,不过复合主键仍然要满足主键的唯一性。

下面使用复合主键:
"_id": { "age": 18, "gender": "男" }

> db.user.insertOne({"_id":{"age":18,"gender":"男" },"money":1000,"name":"吴九"})
{ "acknowledged" : true, "insertedId" : { "age" : 18, "gender" : "男" } }
> db.user.find()
{ "_id" : "1", "money" : 1000, "name" : "刘一" }
{ "_id" : "2", "money" : 1000, "name" : "陈二" }
{ "_id" : "3", "money" : 1000, "name" : "张三" }
{ "_id" : "4", "money" : 1000, "name" : "李四" }
{ "_id" : "5", "money" : 1000, "name" : "王五" }
{ "_id" : "6", "money" : 1000, "name" : "赵六" }
{ "_id" : "7", "money" : 1000, "name" : "孙七" }
{ "_id" : ObjectId("5fff6a78025e5d9ea86e18cc"), "money" : 1000, "name" : "周八" }
{ "_id" : { "age" : 18, "gender" : "男" }, "money" : 1000, "name" : "吴九" }
> 

复合主键仍然要满足唯一性,复合主键里面,若每个key:value 相同,而值不同,表示不同的主键,与顺序相关。

> db.user.insertOne({"_id":{"age":18,"gender":"男" },"money":1000,"name":"郑十"})
WriteError({
	"index" : 0,
	"code" : 11000,
	"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: { age: 18.0, gender: \"男\" } }",
	"op" : {
		"_id" : {
			"age" : 18,
			"gender" : "男"
		},
		"money" : 1000,
		"name" : "郑十"
	}
}) :
WriteError({
	"index" : 0,
	"code" : 11000,
	"errmsg" : "E11000 duplicate key error collection: test.user index: _id_ dup key: { _id: { age: 18.0, gender: \"男\" } }",
	"op" : {
		"_id" : {
			"age" : 18,
			"gender" : "男"
		},
		"money" : 1000,
		"name" : "郑十"
	}
})
WriteError@src/mongo/shell/bulk_api.js:458:48
mergeBatchResults@src/mongo/shell/bulk_api.js:855:49
executeBatch@src/mongo/shell/bulk_api.js:919:13
Bulk/this.execute@src/mongo/shell/bulk_api.js:1163:21
DBCollection.prototype.insertOne@src/mongo/shell/crud_api.js:264:9
@(shell):1:1
> 

[慕课手记同步:mongo-02-创建文档和ObjectId] https://www.imooc.com/article/314364


欢迎关注文章同步公众号"黑桃"

mongo-02-创建文档和ObjectId

上一篇:在Linux C中获得PTY的最简单方法


下一篇:使用Docker 部署MongoDB