跳至主要內容

MongoDB 与 Go 操作 MongoDB

逸尘.Lycodx大约 9 分钟后端gomongodbdb

MongoDB与Go操作MongoDB

mongodb官网:https://www.mongodb.com/open in new window

一、安装与启动MongoDB

下载地址:https://www.mongodb.com/try/download/communityopen in new window

windows安装直接到下载地址中点击下载就行了,这里主要讲Linux下载安装步骤

1、下载

CentOs

# 下载
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-5.0.9.tgz
# 解压
tar -xzvf mongodb-linux-x86_64-rhel70-5.0.9.tgz 
# 移动到 /usr/local 目录下
mv mongodb-linux-x86_64-rhel70-5.0.9 /usr/local/mongodb

2、修改系统配置文件

# 编辑 /etc/profile 文件
vim /etc/profile
# 将如下内容加入到文件末尾
export MONGODB_HOME=/usr/local/mongodb
export PATH=$PATH:$MONGODB_HOME/bin
# 刷新系统配置文件
source /etc/profile

3、检查系统配置是否生效

# 输入
mongo
# 显示版本和一些连接信息
MongoDB shell version v5.0.9
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&gssapiServiceName=mongodb
Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed: SocketException: Error connecting to 127.0.0.1:27017 :: caused by :: Connection refused :
connect@src/mongo/shell/mongo.js:372:17
@(connect):2:6
exception: connect failed
exiting with code 1

4、添加一些文件

1.数据保存文件

cd /usr/local/mongodb/bin
mkdir data

2.日志输出文件

cd /usr/local/mongodb/bin
mkdir logs
cd logs
touch mongodb.log

3.mongodb配置文件

新建文件mongodb.conf,添加如下内容:

#数据库路径
dbpath=/usr/local/mongodb/bin/data
#日志输出文件路径
logpath=/usr/local/mongodb/bin/logs/mongodb.log
#错误日志采用追加模式
logappend=true
#启用日志文件,默认启用
journal=true
#这个选项可以过滤掉一些无用的日志信息,若需要调试使用请设置为false
quiet=true
#端口号 默认为27017
port=27017
#允许远程访问
bind_ip=0.0.0.0
#开启子进程
fork=true
#开启认证,必选先添加用户,先不开启(不用验证账号密码)
#auth=true

5、启动&查看

# 后台运行
mongod --config /usr/local/mongodb/bin/mongodb.conf

# 查看27017端口
netstat -lanp | grep 27017
# 查看mongodb进程
ps aux | grep mongodb

启动成功后就可以使用工具(navicat、goland、vscode)进行连接了。

tips

如果启动时报错就将配置文件中的fork=true注释掉,这样错误会提示的更清晰,方便排查

6、关闭服务

mongod -shutdown -dbpath=/usr/local/mongodb/bin/data

结合以前的知识,我们可以将mongodbsystemd托管,这样启动停止就方便很多,还有systemd的进程守护

7、可视化工具

navicat 15+golangvscode

官方工具下载:https://www.mongodb.com/try/download/compassopen in new window

8、其他系统安装

1、Debian

参考1:https://mongodb.net.cn/manual/tutorial/install-mongodb-on-debian/#install-mongodb-community-editionopen in new window

参考2:https://cloud.tencent.com/developer/article/1174423open in new window

debian系统安装完成后直接是systemctl启动,如果需要能直连上,需要修改一下配置文件

nano /ect/mongodb.conf
# 将监听地址改为全0
bindIp: 0.0.0.0
1661020350716

二、常规操作

友情链接1:传送门open in new window

友情链接2:https://www.liwenzhou.com/posts/Go/go_mongodb/open in new window

三、go语言操作

参考资料:

1、https://mongodb.net.cn/manual/core/aggregation-pipeline/open in new window

我们使用官方的包

后面全部代码需要用到的引入

import (
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

1、连接数据库

func Connect() (*mongo.Client, error) {
	connect := options.Client().ApplyURI("mongodb://localhost:27017")
	client, err := mongo.Connect(context.TODO(), connect)
	if err != nil {
		return nil, err
	}
	//检查连接
	err = client.Ping(context.TODO(), nil)
	if err != nil {
		return nil, err
	}
	return client, err
}

连接:mongodb://127.0.0.1:27017/?replicaSet=rs0&readPreference=primary&ssl=false&connect=direct

2、查询

我们有一个测试库api-test里面有个测试集合0.0.0.0

数据格式示例:

{
  "host": "127.0.0.1:10000",
  "path": "/dr/1/",
  "name": "",
  "response": {
    "code": 200,
    "message": ""
  },
  "pattern": "/dr/1/",
  "createtime": {
    "seconds": 1656479991
  }
}

1.数量统计查询

func GetTotal(ctx context.Context, client *mongo.Client) (int64, error) {
	collection := client.Database("api-test").Collection("0.0.0.0")
	count, err := collection.CountDocuments(ctx, bson.D{})
	if err != nil {
		return 0, err
	}
	return count, nil
}

这里用到的是CountDocuments方法,bson.D是过滤条件,这里没有条件就给空,后面会详细介绍这个bson

众所周知

mongodb 查询数量这个方法在数据量特别大时查询速度非常慢,如果我们在不需要任何筛选条件的前台下,可以采用EstimatedDocumentCount方法取代CountDocuments

2.分组查询

参考文档:https://www.mongodb.com/docs/manual/reference/command/aggregate/open in new window

假设我需要对response下面的code进行分组查询

获取这种字段用response.code

func GroupQuery(ctx context.Context, client *mongo.Client) (results []bson.M, err error) {
	collection := client.Database("api-test").Collection("0.0.0.0")
	pipeline := mongo.Pipeline{
		//1.$group为分组固定写法
		//2."_id"是固定写法,会根据后面的字段进行分组,字段必须$开头
		//3.total为自定义的键,$sum为统计固定写法,1也是固定写法
		{{Key: "$group", Value: bson.D{{Key: "_id", Value: "$response.code"}, {Key: "total", Value: bson.D{{Key: "$sum", Value: 1}}}}}},
	}
	//这里得到的是一个游标
	cur, err := collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
    //因为得到的是list,所以循环
	for cur.Next(ctx) {
		var m bson.M  //如果业务需要可以自己构造结构体进行赋值
		if err := cur.Decode(&m); err != nil {
			return nil, err
		}
		results = append(results, m)
	}
	return results, nil
}

过滤条件先声明,再赋值写法

var pipeline mongo.Pipeline
pipeline = append(pipeline, bson.D{{Key: "$group", Value: bson.D{{Key: "_id", Value: "$response.code"}, {Key: "total", Value: bson.D{{Key: "$sum", Value: 1}}}}}})

如果需要对response.codehost两个字段进行分组查询

修改一下过滤部分

pipeline := mongo.Pipeline{
	{{Key: "$group", Value: bson.D{{Key: "_id", Value: bson.M{"host": "$host", "responseCode": "$response.code"}}, {Key: "total", Value: bson.D{{Key: "$sum", Value: 1}}}}}},
}

mongodb中操作是可以在管道中传递的,就是说可以进行一系列操作

比如:我们想将hostpath组合加上response.code进行排序统计结果,再按照结果进行排序

// 我们这里修改一下过滤部分
pipeline := mongo.Pipeline{
	//$project是固定写法,用于修改字段;hostPath为自定义键;$concat为连接字段的固定写法
	{{Key: "$project", Value: bson.D{{Key: "hostPath", Value: bson.D{{Key: "$concat", Value: bson.A{"$host","$path"}}}}}}},
	{{Key: "$group", Value: bson.D{{Key: "_id", Value: bson.M{"hostPath": "$hostPath", "responseCode": "$response.code"}}, {Key: "total", Value: bson.D{{Key: "$sum", Value: 1}}}}}},
		//$sort为排序固定写法,1表示从小到大,-1表示从大到小
	{{Key: "$sort", Value: bson.D{{Key: "total", Value: 1}}}},
}

提示

根据已知资料我得出分组查询只能有一个_id和自定义的统计字段

3.模糊查询

我们模糊匹配一下path有多少各带dr

1、过滤查询

func LikeQuery(ctx context.Context, client *mongo.Client) (int64, error) {
	collection := client.Database("api-test").Collection("0.0.0.0")
	filter := bson.D{
		//不是聚合查询,字段都不需要$符号
		//$regex为固定写法,后面支持正则表达式
		{Key: "path", Value: bson.D{{Key: "$regex", Value: primitive.Regex{Pattern: ".*dr.*"}}}},
	}
	count, err := collection.CountDocuments(ctx, filter)
	if err != nil {
		return 0, err
	}
	return count, nil
}

提示

根据已知资料我觉得模糊查询只能支持string类型的字段

2、聚合查询

func LikeQuery(ctx context.Context, client *mongo.Client) (results []bson.M, err error) {
	collection := client.Database("api-test").Collection("0.0.0.0")
	pipeline := mongo.Pipeline{bson.D{
		{Key: "$match", Value: bson.D{{Key: "path", Value: bson.D{{Key: "$regex", Value: ".*.dr.*"}}}}},
	}}
	cur, err := collection.Aggregate(ctx, pipeline)
	if err != nil {
		return nil, err
	}
	for cur.Next(ctx) {
		var m bson.M
		if err := cur.Decode(&m); err != nil {
			return nil, err
		}
		results = append(results, m)
	}
	return results, nil
}

提示

模糊查询时当遇到正则关键字如:? 时,需要进行转义 \?

4.查询单条数据

func GetOne(ctx context.Context, client *mongo.Client) (bson.M, error) {
	collection := client.Database("api-test").Collection("0.0.0.0")
	filter := bson.D{
		//$eq为固定写法是等于的意思
		{Key: "host", Value: bson.D{{"$eq", "127.0.0.1:10000"}}},
	}
	var m bson.M
	err := collection.FindOne(ctx, filter).Decode(&m)
	if err != nil {
		return nil, err
	}
	return m, nil
}

先声明再赋值的写法

var filter bson.D
filter = append(filter, bson.E{Key: "host", Value: bson.D{{"$eq", "127.0.0.1:10000"}}})

说明

bson里有A D E M等等,具体是什么意思,大家可以去看一下代码

上面代码中的{}非常的多,但是不用轻易调整它们的位置,说不定就会报错,还找不到原因

5.区间查询

1、过滤查询

// >= 100 < 300
filter := bson.D{}
filter = append(filter, bson.E{
    Key: "response.code", Value: bson.D{
        {Key: "$gte", Value: 100}, {Key: "$lte", Value: 300},
    },
})

2、聚合查询

// >= 100 < 300
pipeline := mongo.Pipeline{
	bson.D{{Key: "$match", Value: bson.D{
		{Key: "response.code", Value: bson.D{{Key: "$gte", Value: 100}}},
		{Key: "response.code", Value: bson.D{{Key: "$lte", Value: 300}}},
	}}},
}

6.分页查询

方法一:

pipeline := mongo.Pipeline{}
pipeline = append(pipeline, bson.D{{Key: "$skip", Value: 1}})
pipeline = append(pipeline, bson.D{{Key: "$limit", Value: 10}})

提示

该方法需要单独查询总页数

① 使用CountDocuments查询总页数,该方法不能分组,在过滤条件复杂的情况下有些不支持

collection.CountDocuments(ctx, bson.D{})

② 使用Aggregate+count查询总页数(推荐)

pipeline := mongo.Pipeline{
    bson.D{{Key: "$match", Value: bson.D{{Key: "$eq", Value: bson.D{{"$eq", "127.0.0.1:10000"}}}}}},
    // 可以补充更多过滤条件
    bson.D{{Key: "$count", Value: "total"}},
}
collection.Aggregate(ctx, pipeline)

方法二:

pipeline := mongo.Pipeline{
    bson.D{{"$facet", bson.D{
        {"data", bson.A{
            // 其他条件往上加就行
            bson.D{{Key: "$skip", Value: 1}},
            bson.D{{Key: "$limit", Value: 10}},
        }},
        {"total", bson.D{{"$count", "total"}}}}},
    },
}
collection.Aggregate(ctx, pipeline)

提示

该方法在数据量大的时候不能排序,排序会导致查询速度非常慢

7.范围查询

// 查询状态码为 200 404 的集合
arr := []int{200, 404}
pipeline := mongo.Pipeline{
	bson.D{{Key: "$match", Value: bson.D{
		{Key: "response.code", Value: bson.D{{Key: "$in", Value: arr}}},
	}}},
}

8.组合查询

1、and

1.过滤查询

filter := bson.M{
"$and": []bson.M{
		{"host": "127.0.0.1"},
		{"path": "/api"},
	},
}
count, err := Collection.CountDocuments(ctx, filter)

2.聚合查询

pipeline := mongo.Pipeline{
	bson.D{{Key: "$match", Value: bson.M{
		"$and": []bson.M{
			{"host": "127.0.0.1"},
			{"path": "/api"},
		},
	}}},
}
cur, err := Collection.Aggregate(ctx, pipeline)

2、or

1.过滤查询

// 查询状态码在200~300 或 400~500 的数量
func Query(ctx context.Context, client *mongo.Client) (int64, error) {
	filter := bson.D{{Key: "$or", Value: bson.A{
		bson.D{{Key: "response.code", Value: bson.M{"$gte": 200, "$lt": 300}}},
		bson.D{{Key: "response.code", Value: bson.M{"$gte": 400, "$lt": 500}}},
	}}}
	collection := client.Database("api-test").Collection("0.0.0.0")
	count, err := collection.CountDocuments(ctx, filter)
	if err != nil {
		return 0, err
	}
	return count, nil
}

2.聚合查询

// 查询状态码在200~300 或 400~500 的集合
func Query(ctx context.Context, client *mongo.Client) (results []bson.M, err error) {
	pipeline := mongo.Pipeline{
		bson.D{{Key: "$match", Value: bson.D{{Key: "$or", Value: bson.A{
			bson.D{{Key: "response.code", Value: bson.M{"$gte": 200, "$lt": 300}}},
			bson.D{{Key: "response.code", Value: bson.M{"$gte": 400, "$lt": 500}}},
		}}}}},
	}
	collection := client.Database("api-test").Collection("0.0.0.0")
	cur, err := collection.Aggregate(ctx, pipeline)
	fmt.Println(cur)
	if err != nil {
		return nil, err
	}
	for cur.Next(ctx) {
		var m bson.M
		if err := cur.Decode(&m); err != nil {
			return nil, err
		}
		results = append(results, m)
	}
	return results, nil
}

3、新增

// data 是个json数据
collection.InsertOne(ctx, data)
// 批量插入
collection.InsertMany(ctx, data)

4、修改

// filter是过滤条件;data需要更新的json数据
collection.UpdateOne(ctx, filter, data)
// 多条更新
collection.UpdateMany(ctx, filter, data)

5、删除

1、根据id单个删除

func DeleteById(ctx context.Context, client *mongo.Client, id string) (bool, error) {
	xid, err := primitive.ObjectIDFromHex(id)
	if err != nil {
		return false, err
	}
	collection := client.Database("api-test").Collection("0.0.0.0")
	result, err := collection.DeleteOne(ctx, bson.D{{Key: "_id", Value: xid}})
	if err != nil {
		return false, err
	}
	if result.DeletedCount == 1 {
		return true, nil
	}
	return false, nil
}

2、根据多个id批量删除

// 示例:ids = "62d81a724f635f8d2ed1f961,62d81a724f635f8d2ed1f962"
func DeleteManyByIds(ctx context.Context, client *mongo.Client, ids string) (bool, error) {
	idArr := strings.Split(ids, ",")
	var xdiArr []primitive.ObjectID
	for _, id := range idArr {
		xid, err := primitive.ObjectIDFromHex(id)
		if err != nil {
			return false, err
		}
		xdiArr = append(xdiArr, xid)
	}
	collection := client.Database("api-test").Collection("0.0.0.0")
	result, err := collection.DeleteMany(ctx, bson.D{{Key: "_id", Value: bson.D{{Key: "$in", Value: xdiArr}}}})
	if err != nil {
		return false, err
	}
	if result.DeletedCount == int64(len(idArr)) {
		return true, nil
	}
	return false, nil
}

四、其他

1、常见问题

查询时如果遇到报错:

PlanExecutor error during aggregation :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting

大意是:排序超出了104857600字节的内存限制,但未选择外部排序

解决办法:

// 设置 allowDiskUse=true
opt := options.Aggregate().SetAllowDiskUse(true)
cur, err := Collection.Aggregate(ctx, pipeline, opt)

2、速查表

mongodb写法含义
$gt>
$gte>=
$lt<
$lte<=
$eq=
$ne!=
$inin
$ninno in
$oror
$andand
$groupgroup
$sumsum
$concatconcat
$project
$sortsort
$limitlimit
$match匹配
$set
$exist
$skip分页的数量
$regex正则匹配
$count
$facet
上次编辑于: