0%

mongodb使用和操作教程

1. 数据结构

1.1 数据类型

  • null :null 类型用于表示空值或不存在的字段。{"x":null}
  • 布尔类型:布尔类型的值可以为 true 或者 false。{"x":true}
  • 数值类型:shell 默认使用 64 位的浮点数来表示数值类型。{"x":3.14}。对于整数,可以使用 NumberInt 或 NumberLong 类,它们分别表示 4 字节和 8 字节的有符号整数。对于整数,可以使用 NumberInt 或 NumberLong 类,它们分别表示 4 字节和 8 字节的有符号整数。{"x":NumberInt("3") {"x":NumberLong("3")
  • 字符串类型:{"x":"foobar"}
  • 日期类型:MongoDB 会将日期存储为 64 位整数,表示自 Unix 纪元(1970 年 1 月 1 日)以来的毫秒数,不包含时区信息。{"x":new Date()}
  • 正则表达式:{"x": /foobar/i}
  • 数组类型:{"x":["a","b","c"]}
  • 内嵌文档:{"x":{"foo":"bar"}}
  • Object ID:  Object ID 是一个 12 字节的 ID,是文档的唯一标识。{"x": ObjectId()}
  • 二进制数据: 二进制数据是任意字节的字符串,不能通过 shell 操作。如果要将非 UTF-8 字符串存入数据库,那么使用二进制数据是唯一的方法。
  • 代码: MongoDB 还可以在查询和文档中存储任意的 JavaScript 代码。

2. 基础操作

2.1 数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 查看所有数据库
show dbs

# 查看当前在哪个数据库
> db
study

# 创建或进入数据库
use study

# 删除数据库
db.dropDatabase()

# 查看数据库的所有集合
show collections

2.2 集合

可以使用 deleteMany 来删除一个集合中的所有文档:

1
2
3
4
5
6
7
8
9
10
11
> db.movies.find() 
> { "_id" : 0, "title" : "Top Gun", "year" : 1986 }
> { "_id" : 1, "title" : "Back to the Future", "year" : 1985 }
> { "_id" : 3, "title" : "Sixteen Candles", "year" : 1984 }
> { "_id" : 4, "title" : "The Terminator", "year" : 1984 }
> { "_id" : 5, "title" : "Scarface", "year" : 1983 }
>
> db.movies.deleteMany({})
> { "acknowledged" : true, "deletedCount" : 5 }
>
> db.movies.find()

删除文档的操作通常会比较快。不过,如果想清空整个集合,那么使用 drop 直接删除集合,然后在这个空集合中重建各项索引会更快:

1
2
> db.movies.drop()
> true

2. 增删文档

2.1 插入文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# insertOne 
# 会为文档自动添加一个 "_id" 键(如果你没有提供的话),并将其保存到 MongoDB 中。
> db.movies.insertOne({"title""Stand by Me"})


# insertMany
# 此方法可以将一个文档数组传递到数据库。这是一种更加高效的方法,因为代码不会为插入的每个文档去请求数据库,而是会批量插入它们。

> db.movies.drop() true
> db.movies.insertMany([{"title" : "Ghostbusters"}, {"title" : "E.T."}, {"title" : "Blade Runner"}]);
> db.movies.find()
{ "_id" : ObjectId("572630ba11722fac4b6b4996"), "title" : "Ghostbusters" }
{ "_id" : ObjectId("572630ba11722fac4b6b4997"), "title" : "E.T." }
{ "_id" : ObjectId("572630ba11722fac4b6b4998"), "title" : "Blade Runner" }

2.2 删除文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
> db.movies.find() 
> { "_id" : 0, "title" : "Top Gun"}
> { "_id" : 1, "title" : "Back to the Future"}
> { "_id" : 3, "title" : "Sixteen Candles"}
> { "_id" : 4, "title" : "The Terminator"}
> { "_id" : 5, "title" : "Scarface"}

> db.movies.deleteOne({"_id" : 4})
> { "acknowledged" : true, "deletedCount" : 1 }

> db.movies.find()
> { "_id" : 0, "title" : "Top Gun"}
> { "_id" : 1, "title" : "Back to the Future"}
> { "_id" : 3, "title" : "Sixteen Candles"}
> { "_id" : 5, "title" : "Scarface"}




> db.movies.find()
> { "_id" : 0, "title" : "Top Gun", "year" : 1986 }
> { "_id" : 1, "title" : "Back to the Future", "year" : 1985 }
> { "_id" : 3, "title" : "Sixteen Candles", "year" : 1984 }
> { "_id" : 4, "title" : "The Terminator", "year" : 1984 }
> { "_id" : 5, "title" : "Scarface", "year" : 1983 }
>
> db.movies.deleteMany({"year" : 1984})
> { "acknowledged" : true, "deletedCount" : 2 }
>
> db.movies.find()
> { "_id" : 0, "title" : "Top Gun", "year" : 1986 }
> { "_id" : 1, "title" : "Back to the Future", "year" : 1985 }
> { "_id" : 5, "title" : "Scarface", "year" : 1983 }

在 MongoDB 3.0 之前,remove 是删除文档的主要方法。在 3.0 版本的服务器端发布时,MongoDB 驱动程序引入了 deleteOne 和 deleteMany 方法,并且从 MongoDB 3.2 起,shell 也开始支持这些方法。

尽管 remove 仍然支持向后兼容,但你应该在应用程序中使用 deleteOne 和 deleteMany。

3. 更新文档

更新文档是原子操作:如果两个更新同时发生,那么首先到达服务器的更新会先被执行,然后再执行下一个更新。

updateOne 和 updateMany 都将筛选文档作为第一个参数,将变更文档作为第二个参数,后者对要进行的更改进行描述。
replaceOne 同样将筛选文档作为第一个参数,但第二个参数是一个用来替换所匹配的筛选文档的新文档。

3.1 replaceOne 替换文档

replaceOne 会用新文档完全替换匹配的文档。这对于进行大规模模式迁移的场景非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{ "_id" : ObjectId("4b2b9f67a1f631733d917a7a"), "name" : "joe", "friends" : 32, "enemies" : 2 }


# 修改
> var joe = db.users.findOne({"name" : "joe"});
> joe.relationships = {"friends" : joe.friends, "enemies" : joe.enemies};
> { "friends" : 32, "enemies" : 2 }
>
> joe.username = joe.name;
> "joe"
>
> delete joe.friends;
> true
>
> delete joe.enemies;
> true
>
> delete joe.name;
> true
>
> db.users.replaceOne({"name" : "joe"}, joe);



{ "_id" : ObjectId("4b2b9f67a1f631733d917a7a"), "username" : "joe", "relationships" : { "friends" : 32, "enemies" : 2 } }

3.2 updateOne 和 更新运算符

1
2
3
4
5
6
7
8
9

{ "_id" : ObjectId("4b253b067525f35f94b60a31"), "url" : "example-domain", "pageviews" : 52 }

> db.analytics.updateOne({"url" : "example-domain"}, {"$inc" : {"pageviews" : 1}})
> { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }


> db.analytics.findOne()
> { "_id" : ObjectId("4b253b067525f35f94b60a31"), "url" : "example-domain", "pageviews" : 53 }

使用更新运算符时,"_id" 的值是不能改变的。(注意,整个文档替换时是可以改变 "_id" 的。)其他键值,包括其他唯一索引的键,都是可以更改的。

1. “$set” 修饰符

“$set” 用来设置一个字段的值。如果这个字段不存在,则创建该字段。这对于更新模式或添加用户定义的键来说非常方便。

1
2
3
4
5
6
7
8
9
10
> db.users.findOne() 
> { "_id" : ObjectId("4b253b067525f35f94b60a31"), "name" : "joe", "age" : 30, "sex" : "male", "location" : "Wisconsin" }



> db.users.updateOne({"_id" : ObjectId("4b253b067525f35f94b60a31")}, {"$set" : {"favorite book" : "War and Peace"}})


> db.users.findOne()
> { "_id" : ObjectId("4b253b067525f35f94b60a31"), "name" : "joe", "age" : 30, "sex" : "male", "location" : "Wisconsin", "favorite book" : "War and Peace" }

如果用户觉得其实喜欢的是另外一本书,则可以再次使用 “$set” 来修改这个值:

1
2
3
4
5
6
7
8
9
> db.users.updateOne({"name" : "joe"},  {"$set" : {"favorite book" : "Green Eggs and Ham"}})

# "$set" 甚至可以修改键的类型。如果用户觉得喜欢很多本书,那么可以将 "favorite book" 键的值更改为一个数组:

> db.users.updateOne({"name" : "joe"}, {"$set" : {"favorite book" : ["Cat's Cradle", "Foundation Trilogy", "Ender's Game"]}})

# 如果用户发现自己其实不爱读书,则可以用 "$unset" 将这个键完全删除:

> db.users.updateOne({"name" : "joe"}, {"$unset" : {"favorite book" : 1}})

应该始终使用 $ 修饰符来增加、修改或删除键。

2. 数组运算符

添加元素。如果数组已存在,”$push” 就会将元素添加到数组末尾;如果数组不存在,则会创建一个新的数组。

1
2
3
4
5
6
7
8
9
10
> db.blog.posts.findOne() 
> { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "..." }

> db.blog.posts.updateOne({"title" : "A blog post"}, {"$push" : {"comments" : {"name" : "joe", "email" : "joe@example.com", "content" : "nice post."}}})
> { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }



> db.blog.posts.findOne()
> { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "title" : "A blog post", "content" : "...", "comments" : [ { "name" : "joe", "email" : "joe@example.com", "content" : "nice post." } ] }

“$pull” 用于删除与给定条件匹配的数组元素。

1
2
3
4
5
6
7
> db.lists.insertOne({"todo" : ["dishes", "laundry", "dry cleaning"]})


> db.lists.updateOne({}, {"$pull" : {"todo" : "laundry"}})

> db.lists.findOne()
> { "_id" : ObjectId("4b2d75476cc613d5ee930164"), "todo" : [ "dishes", "dry cleaning" ] }

“$pull” 会删除所有匹配的文档,而不仅仅是一个匹配项。如果你有一个数组 [1, 1, 2, 1] 并执行 pull 1,那么你将得到只有一个元素的数组 [2]。

3.3 Upsert 新增或更新

upsert 是一种特殊类型的更新。如果找不到与筛选条件相匹配的文档,则会以这个条件和更新文档为基础来创建一个新文档;如果找到了匹配的文档,则进行正常的更新。

1
> db.analytics.updateOne({"url" : "/blog"}, {"$inc" : {"pageviews" : 1}}, {"upsert" : true})
1. 只有 $set

新增和更新都会改变 6 个字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func (repo *wxGroupInfoRepo) StoreWxGroupInfo(ctx context.Context, dao *daowxGroup.WxGroupInfoDao) (err error) {  
app, biz := utils.GetAppBiz(ctx)
filter := bson.M{"wx_group_id": dao.WxGroupId}
update := bson.M{
"$set": bson.M{
"app": app,
"biz": biz,
"wx_group_id": dao.WxGroupId,
"bot_id": dao.BotId,
"create_time": time.Now(),
"update_time": time.Now(),
},
}
opts := options.Update().SetUpsert(true)
_, err = repo.data.writeDB.Collection(daowxGroup.GetWxGroupInfoTableName()).UpdateOne(ctx, filter, update, opts)
if err != nil {
return err
}
return
}
1.2 $setOnInsert 和 $set共用

有时候需要在创建文档时对字段进行设置,但在后续更新时不对其进行更改。这就是 $setOnInsert 的作用。$setOnInsert 是一个运算符,它只会在插入文档时设置字段的值。

新增的时候会增加 6 个字段,更新会更新 3 个字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func (repo *wxGroupInfoRepo) StoreWxGroupInfo(ctx context.Context, dao *daowxGroup.WxGroupInfoDao) (err error) {  
app, biz := utils.GetAppBiz(ctx)
filter := bson.M{"wx_group_id": dao.WxGroupId}
update := bson.M{
"$setOnInsert": bson.M{
"app": app,
"biz": biz,
"create_time": time.Now(),
},
"$set": bson.M{
"wx_group_id": dao.WxGroupId,
"bot_id": dao.BotId,
"update_time": time.Now(),
},
}
opts := options.Update().SetUpsert(true)
_, err = repo.data.writeDB.Collection(daowxGroup.GetWxGroupInfoTableName()).UpdateOne(ctx, filter, update, opts)
if err != nil {
return err
}
return
}

3.4 updateMany 更新多个文档

updateOne 只会更新找到的与筛选条件匹配的第一个文档。如果匹配的文档有多个,它们将不会被更新。要修改与筛选器匹配的所有文档,请使用 updateMany。

1
2
3
4
5
> db.users.insertMany([  {birthday: "10/13/1978"},  {birthday: "10/13/1978"},  {birthday: "10/13/1978"}])
> { "acknowledged" : true, "insertedIds" : [ ObjectId("5727d6fc6855a935cb57a65b"), ObjectId("5727d6fc6855a935cb57a65c"), ObjectId("5727d6fc6855a935cb57a65d") ] }
>
> db.users.updateMany({"birthday" : "10/13/1978"}, {"$set" : {"gift" : "Happy Birthday!"}})
> { "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }

4. 查询文档

空的查询文档({})会匹配集合中的所有内容。如果 find 没有给定查询文档,则默认为 {}。

1
> db.c.find()

4.1 复合条件

在查询文档中加入多个键–值对,以将多个查询条件组合在一起,这样的查询条件会被解释为“条件 1 AND 条件 2AND…AND 条件 N ”。

1
> db.users.find({"username" : "joe", "age" : 27})

4.2 指定返回的键

1
2
3
4
5
6
7
8
9

> db.users.find({}, {"username" : 1, "email" : 1})
> { "_id" : ObjectId("4ba0f0dfd22aa494fd523620"), "username" : "joe", "email" : "joe@example.com" }
# 从以上输出可以看到,默认情况下 "_id" 键总是会被返回,即使没有指定要返回这个键。


> db.users.find({}, {"username" : 1, "_id" : 0})
> { "username" : "joe" }
# 强制去除 _id

4.3 查询条件

1. 大于小于
1
2
3
4
5
6
7
8
9
# "$lt"、"$lte"、"$gt" 和 "$gte" 都属于比较运算符,分别对应 <、<=、> 和 >=。


# 这样就可以查找到 "age" 字段大于等于 18 并且小于等于 30 的所有文档了。
> db.users.find({"age" : {"$gte" : 18, "$lte" : 30}})


#"$ne" 了,它表示“不相等”。如果想找到所有用户名不为 joe 的用户,可以像下面这样查询:
> db.users.find({"username" : {"$ne" : "joe"}})
2. Or 运算
1
2
3
4
5
6
7
8
9
10
# "$in" 的用法非常灵活,可以指定不同类型的条件和值。
> db.users.find({"user_id" : {"$in" : [12345, "joe"]}})


# 与 "$in" 相反的是 "$nin",此运算符会返回与数组中所有条件都不匹配的文档。
> db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})


# "$or" 会接受一个包含所有可能条件的数组作为参数。
> db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})

虽然总是可以使用 "$or",但只要有可能就应该使用 "$in",因为查询优化器可以更高效地对其进行处理。

3. Null
1
2
3
4
5
6
7
8
> db.c.find() 
> { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null }
> { "_id" : ObjectId("4ba0f0dfd22aa494fd523622"), "y" : 1 }
> { "_id" : ObjectId("4ba0f148d22aa494fd523623"), "y" : 2 }


> db.c.find({"y" : null})
> { "_id" : ObjectId("4ba0f0dfd22aa494fd523621"), "y" : null }
4. 正则表达式
1
2
3
# 如果要查找所有用户名为 Joe 或 joe 的用户,那么可以使用正则表达式进行不区分大小写的匹配:

> db.users.find( {"name" : {"$regex" : /joe/i } })

4.4 查询数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

> db.food.insertOne({"fruit" : ["apple", "banana", "peach"]})
> db.food.find({"fruit" : "banana"}) # 也能查询到


# 可以使用 "$all" 查询来找到同时包含元素 "apple" 和 "banana" 的文档:
> db.food.insertOne({"_id" : 1, "fruit" : ["apple", "banana", "peach"]})
> db.food.insertOne({"_id" : 2, "fruit" : ["apple", "kumquat", "orange"]})
> db.food.insertOne({"_id" : 3, "fruit" : ["cherry", "banana", "apple"]})


> db.food.find({fruit : {$all : ["apple", "banana"]}})
>
> {"_id" : 1, "fruit" : ["apple", "banana", "peach"]}
> {"_id" : 3, "fruit" : ["cherry", "banana", "apple"]}

5. 参考文档

  • MongoDB权威指南(第3版)
给作者打赏,可以加首页微信,咨询作者相关问题!