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 12
| > 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 15
|
> db.movies.insertOne({"title""Stand by Me"})
> 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 34 35
| > 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 26
| { "_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 11
| > 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"}})
> db.users.updateOne({"name" : "joe"}, {"$set" : {"favorite book" : ["Cat's Cradle", "Foundation Trilogy", "Ender's Game"]}})
> 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 没有给定查询文档,则默认为 {}。
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" }
> db.users.find({}, {"username" : 1, "_id" : 0}) > { "username" : "joe" }
|
4.3 查询条件
1. 大于小于
1 2 3 4 5 6 7 8 9
|
> db.users.find({"age" : {"$gte" : 18, "$lte" : 30}})
> db.users.find({"username" : {"$ne" : "joe"}})
|
2. Or 运算
1 2 3 4 5 6 7 8 9 10 11
| > db.users.find({"user_id" : {"$in" : [12345, "joe"]}})
> db.raffle.find({"ticket_no" : {"$nin" : [725, 542, 390]}})
> db.raffle.find({"$or" : [{"ticket_no" : 725}, {"winner" : true}]})
|
虽然总是可以使用 "$or",但只要有可能就应该使用 "$in",因为查询优化器可以更高效地对其进行处理。
3. Null
1 2 3 4 5 6 7 8 9
| > 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 4
|
> db.users.find( {"name" : {"$regex" : /joe/i } })
|
4.4 查询数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| > db.food.insertOne({"fruit" : ["apple", "banana", "peach"]}) > db.food.find({"fruit" : "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. 参考文档