简单了解

MongoDB在Go中是有官方的API接口库的,不过在官方开发库之前,一直是存在个人开发版(mgo),后来交由社区进行维护了,貌似此人也是与MongoDB官方进行合作进行官方库的开发。

官方库大概是2018年出了第一版正式版,现在已经是正在服役阶段了。而不管是社区版还是官方版,且都是在个人版的基础上进行开发,这里都需要感谢niemeyer

社区版:https://github.com/globalsign/mgo

官方版:https://github.com/mongodb/mongo-go-driver

网上相对较多的资料还是以mgo的为主,比较mongo-go-driver相对较晚,基于同一主干,且mgo也并没有处于废弃状态,所以在使用上依然还是倾向于使用mgo,当然,在官方版更新了好几个版本后,后续会考虑将版本给更换过来。

标识(2019-10)

MongoDB的Go官方库已经是趋于稳定,发布正式版本了,所以作为开发者来说,以后选择官方库应该是最佳选择了,毕竟这个库会一直保持维护的。

但毕竟是一个新的库,网上大部分博客都是关于mgo这个社区维护版本的库使用方式,这有个库也包含了一些例子可以帮助学习。

mongo-go-examples

基础操作

连接serve

mgo:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 解析MongoDB参数
mongo, err := mgo.ParseURL("mongodb://localhost:27017/articles_demo_dev")
// 1、连接MongoDB
s, err := mgo.Dial("mongodb://localhost:27017/articles_demo_dev")
if err != nil {
fmt.Printf("Can't connect to mongo, go error %v\n", err)
panic(err.Error())
}
// 2、选择数据库
coll := session.DB(mongo.Database)

// 3、选择表(集合)
coll.C(g.DbModuleBindFile)

mongo-go-driver:

1
2
3
4
5
6
7
8
9
10
11
// 1, 建立连接
if client, err = mongo.Connect(context.TODO(), "mongodb://localhost:27017", clientopt.ConnectTimeout(5*time.Second)); err != nil {
fmt.Println(err)
return
}

// 2, 选择数据库my_db
database = client.Database("my_db")

// 3, 选择表my_collection
collection = database.Collection("my_collection")

插入数据:

MongoDB的ID是推特很早的时候开源的,tweet的ID。

snowflake: 毫秒/微秒的当前时间 + 机器的ID + 当前毫秒/微秒内的自增ID(每当毫秒变化了, 会重置成0,继续自增)。

mgo:

1
2
3
4
5
6
7
8
9
10
11
idTest := bson.NewObjectId()
err = r.C.Insert(map[string]interface{}{
"test": "ID由开发者指定,否则MongoDB自己生成",
"_id": idTest,
})

// 插入多条数据
r.C.Insert(&Person{"Heln", "31"},
&Person{"Ana", "32"},
&Person{"A3a", "27"},
&Person{"Awa", "24"})

mongo-go-driver:

1
2
3
4
5
6
7
8
9
10
11
if result, err = collection.InsertOne(context.TODO(), map[string]interface{}{
"test": "ID由开发者指定,否则MongoDB自己生成",
"one": "one",
}); err != nil {
fmt.Println(err)
return
}

// _id: 默认生成一个全局唯一ID, ObjectID:12字节的二进制
docId = result.InsertedID.(objectid.ObjectID)
fmt.Println("自增ID:", docId.Hex())

查询

mgo:

1
2
err = r.C.Find(bson.M{"index_no": "123"}).
Select(bson.M{"_id": 0, "test": 1}).One(&tes)

**注意:**查询时,select中除了id外,其他要么是1,要么是0,否则将报错,select是选择返回的参数。

mongo-go-driver:

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
// 4, 按照jobName字段过滤, 想找出jobName=job10, 找出5条
cond = &FindByJobName{JobName: "job10"} // {"jobName": "job10"}

// 5, 查询(过滤 +翻页参数)
if cursor, err = collection.Find(context.TODO(), cond, findopt.Skip(0), findopt.Limit(2)); err != nil {
fmt.Println(err)
return
}

// 延迟释放游标
defer cursor.Close(context.TODO())

// 6, 遍历结果集
for cursor.Next(context.TODO()) {
// 定义一个日志对象
record = &LogRecord{}

// 反序列化bson到对象
if err = cursor.Decode(record); err != nil {
fmt.Println(err)
return
}
// 把日志行打印出来
fmt.Println(*record)
}

删除

mgo:

1
2
r.C.Remove(bson.M{"_id": bson.ObjectIdHex(id)})  // 多条一条记录
r.C.RemoveAll(bson.M{"_id": bson.ObjectIdHex(id)}) // 多条记录

mongo-go-driver:

1
2
3
4
5
6
7
8
9
10
11
// 4, 要删除开始时间早于当前时间的所有日志($lt是less than)
// delete({"timePoint.startTime": {"$lt": 当前时间}})
delCond = &DeleteCond{beforeCond: TimeBeforeCond{Before: time.Now().Unix()}}

// 执行删除
if delResult, err = collection.DeleteMany(context.TODO(), delCond); err != nil {
fmt.Println(err)
return
}

fmt.Println("删除的行数:", delResult.DeletedCount)

其他操作

计算数组大小

MongoDB获取内嵌数组的长度:

https://stackoverflow.com/questions/21387969/mongodb-count-the-number-of-items-in-an-array

1
2
3
4
5
6
> db.mycollection.insert({'foo':[1,2,3,4]})
> db.mycollection.insert({'foo':[5,6,7]})

> db.mycollection.aggregate({$project: { count: { $size:"$foo" }}})
{ "_id" : ObjectId("5314b5c360477752b449eedf"), "count" : 4 }
{ "_id" : ObjectId("5314b5c860477752b449eee0"), "count" : 3 }

使用Go实现,使用mgo,需要用到聚合操作:

数据库数据

1
2
3
4
5
6
7
8
9
10
// mgo
pipe := r.C.Pipe([]bson.M{
{"$match": bson.M{"_id": idTest}},
{"$project": bson.M{"count": bson.M{"$size":"$fileid_bind_module"}}},
})
resp := bson.M{}
err = pipe.One(&resp)
fmt.Println("error is :", err, resp)
// result:
// map[count:3 _id:ObjectIdHex("5bf3f2b290054419149526ef")]

另外一种方法:$size数组元素个数

$addToSet

设置一个字段size来保存数组的大小,不过这种操作无法与$addToSet一起进行使用。因为$addToSet不会在数组中插入重复的数据,而inc操作依然会继续加1

1
2
3
4
5
6
7
err := r.C.Update(map[string]interface{}{
"_id": bson.ObjectIdHex("5bebe0cf7f45aa3270c9e532"),
}, bson.M{
"$addToSet": bson.M{
"test": "1234567890",
},
})

加一个size字段

1
2
3
4
err = db.C("xxxxxx").Update(bson.M{"_id": tmpFile.Id},
bson.M{
"$push": bson.M{"file_id_list": p.FileIdList[0]},
"$inc": bson.M{"list_size": 1}})

MongoDB数组操作

对数组的增删查改,可以学习:MongoDB 数组

mgo查找单层数组

单层数组

代码中需要使用到$elemMatch参数。

[]int{1, 2, 3}

1
2
3
err = r.C.Find(map[string]interface{}{
"test": bson.M{"$elemMatch": bson.M{"$eq": ids},
}}).One(&bind)

mgo查找多层数组

{"_id":"xxxxxx", "testArr": [{"x": "1", "test1": 1}, {"x": "3", "test1": 2}]}

1
2
3
4
5
var testData interface{}
err = r.C.Find(bson.M{
"_id": idTest123,
"testArr": bson.M{"$elemMatch": bson.M{"test1": 1},
}}).One(&testData)

mgo删除数组元素

删除数组元素,会将该子结构完全删除。

删除单层数组

[]int{1, 2, 3}

1
2
3
4
err := r.C.Update(bson.M{
"_id": idTest,
}, bson.M{"$pull": bson.M{"test": "123"},
})
删除两层数组
1
2
3
4
// 删除数组元素
r.C.Update(bson.M{
"_id": idTest123},
bson.M{"$pull": bson.M{"testArr": bson.M{"test1": 1}}})

聚合操作:Aggregation

$match:类似于find中条件匹配;

$unwind:将数组拆开,化成一个个独立信息,除了散开的数组字段不同,其他字段均相同;

$skiplimit:类似于MongoDB中的skip与limit;

$project:修改输入文档的结构。可以用来重命名、增加或删除域,指定获取的字段,没有的字段,也可以用于创建计算结果以及嵌套文档。

1
2
3
4
5
6
7
8
9
10
pipe := r.C.Pipe([]bson.M{
{"$match": bson.M{"_id": idTest123, "product_id": 123}},
{"$unwind": "$testArr"},
{"$skip": 1},
{"$limit": 1},
{"$project": bson.M{"product_id": 1, "one": "$testArr.test1"}},
})
resp := bson.M{}
err = pipe.One(&resp)
//pipe.All(&resp) 匹配数据表中全部符合的数据

用objectID查询某一时间段内数据

ObjectID是由精确到秒的时间戳再加上机器标识等信息组成的,并且建有索引,因此ObjectID本身就可以用于按时间范围查询数据,而不用专门另建时间戳字段和索引。

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
func timeToObjId(t int64) string {
//var t = time.Now().Unix()

// 转换成16进制的字符串,再加补齐16个0
return fmt.Sprintf("%x0000000000000000", t)
}

//往前多少天时间戳
func GetUnixToOldTimeDay(i int) int64 {
day := time.Now().Day()
oldMonth := day - i
t := time.Date(time.Now().Year(), time.Now().Month(), oldMonth, time.Now().Hour(), time.Now().Minute(), time.Now().Second(), time.Now().Nanosecond(), time.Local)
return t.Unix()
}

func objIdOneMonth() (oldMonthId string, nowId string) {
timeUinx := GetUnixToOldTimeDay(30)

oldMonthId = timeToObjId(timeUinx)
nowId = timeToObjId(time.Now().Unix())

return
}

db.C(DbSysMsg).Pipe([]bson.M{
{"$match": bson.M{"_id": bson.M{"$lte": bson.ObjectIdHex(nowId),
"$gt": bson.ObjectIdHex(oldMonthId)}}},
{"$limit": 200}, //每个月1号都清除当前时间的前200条已经空了的消息连接关系
{"$project": bson.M{"count": bson.M{"$size": "user_list_status"}}},
})