CentOs7-64位
#下载安装包
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.4.23.tgz
#解压
mkdir /usr/local/mongodb
tar -zxvf mongodb-linux-x86_64-3.4.23.tgz -C /usr/local/mongodb
#创建必要的文件与文件夹
cd /usr/local/mongodb/mongodb-linux-x86_64-3.4.23/
mkdir data
mkdir logs
touch logs/mongodb.log
chmod -R 777 /usr/local/mongodb/
#创建配置文件并添加配置内容
vim ./bin/mongodb.conf
#简单的配置内容如下
>
storage:
dbPath: "/usr/local/mongodb/mongodb-linux-x86_64-3.4.23/data"
systemLog:
destination: file
path: "/usr/local/mongodb/mongodb-linux-x86_64-3.4.23/logs/mongodb.log"
net:
port: 27022
http:
RESTInterfaceEnabled: true
processManagement:
fork: false
>
#启动mongodb
nohup ./mongod -f mongodb.conf &
#安全启动mongodb
nohup ./mongod -f mongodb.conf --auth &
#连接客户端
./mongo localhost:27022
#优雅的关机
第一种方式: ./mongod --shutdown -f mongodb.conf
第二种方式:
use admin
db.shutdownServer()
自定义的mongodb.conf配置文件详情
storage:
journal:
enabled: true
dbPath: "/usr/local/mongodb/mongodb-linux-x86_64-3.4.23/data"
##是否一个库一个文件夹
directoryPerDB: true
##数据引擎
engine: wiredTiger
##WT引擎配置
wiredTiger:
engineConfig:
##WT最大使用cache(根据服务器实际情况调节)
cacheSizeGB: 1
##是否将索引也按数据库名单独存储
directoryForIndexes: true
journalCompressor:none (默认snappy)
##表压缩配置
collectionConfig:
blockCompressor: zlib (默认snappy,还可选none、zlib)
##索引配置
indexConfig:
prefixCompression: true
systemLog:
destination: file
path: "/usr/local/mongodb/mongodb-linux-x86_64-3.4.23/logs/mongodb.log"
net:
port: 27022
http:
RESTInterfaceEnabled: true
processManagement:
fork: false
#压缩算法 Tips:
#性能: none > snappy >zlib
#压缩比: zlib > snappy > none
#none表示不压缩
mongoDB命令脚本
#内置帮助,显示各种方法说明
> db.help() , db.collection().help()
#查看当前所在的库
> db
#查看所有库
> show dbs
#进入指定的库
> use test
#显示数据库信息
> db.stats()
#查看服务器状态
> db.serverStatus()
#删除数据库
> db.dropDatabase()
#查询集合数量
> db.users.find().size()
#查看当前所在库所有的集合(表)
> show collections
#添加数据
>
var user1 =
{
"userName":"Jaye",
"age": 18,
"country":"heNan"
};
db.users.insert(user1);
>
#查询所有的表中所有数据
> db.users.find()
#通过用户名查询数据
> db.users.find({"userName":"Jaye"})
#以JSON格式展示返回数据
> db.users.find({"userName":"Jaye"}).pretty()
#删除表中所有数据
> db.users.drop()
数据备份、恢复、导出、导入
# 数据备份 mongodump
# -h :指定ip和端口; -d :备份的数据库名称 ; -o:指定备份的路径
# 其本质为:执行查询,然后写入文件;
./mongodump -h localhost:27022 -d test -o /usr/local/mongodb/mongodb-linux-x86_64-3.4.23/backup
# 数据恢复 mongorestore
# --drop 已存在test库则删除原数据库,去掉--drop则是合并
./mongorestore -h localhost:27022 -d test /usr/local/mongodb/mongodb-linux-x86_64-3.4.23/backup/test --drop
#数据导出 mongoexport(针对集合)
#-c :指定导出的集合; -f :要导出的字段; --type:导出的文件格式类型[csv,json]
./mongoexport -h localhost:27022 -d test -c users -f id,username,age,salary --type=csv -o /usr/local/mongodb/mongodb-linux-x86_64-3.4.23/backup/users.csv
#数据导入 mongoimport(针对集合)
#--upsert 表示更新现有数据,如果不适用—upsert,则导入时已经存在的文档会报id重复,数据不再插入,也可以使用—drop删除原有数据
./mongoexport -h localhost:27022 -d test -c users /usr/local/mongodb/mongodb-linux-x86_64-3.4.23/backup/users.csv --upsert
安全启动、权限初始化
# 1.安全启动MongoDB
nohup ./mongod -f mongodb.conf --auth &
# 2.连接客户端创建初始化一个userAdminAnyDatabase权限用户
db.createUser({'user':'boss', 'pwd':'boss', 'roles':[{'role':'userAdminAnyDatabase', 'db':'admin'}]})
# 3.使用初始化的userAdminAnyDatabase权限用户登录,
db.auth("boss","boss");
# 4.切换至test库,并创建test库的readWrite权限用户
use test
db.createUser({'user':'JayeTian','pwd':'abc.123','roles':[{'role':'readWrite','db':'test'}]})
查询已经创建的权限用户
use admin
#查看当前库内已有用户
show users
#查看当前库内可用的roles,默认只有built-in roles
show roles
引入pom
<dependency>
<groupId>org.mongodbgroupId>
<artifactId>mongo-java-driverartifactId>
<version>3.9.0version>
dependency>
3.5.0版本开始加入了对pojo的支持
3.5.0版本增强对json的支持
mongodb原生客户端支持两种document和pojo模式的开发
客户端的基本使用
package com.example.mongodb.controller;
import com.example.mongodb.constant.UsersMongoEnum;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
public class MongodbDocumentTest {
//客户端(内置连接池)
private static final MongoClient client;
//数据库
private static final MongoDatabase db;
//文档集合(表)
private static final MongoCollection<Document> doc;
static {
client = new MongoClient("192.168.227.131", 27022);
db = client.getDatabase("test");
doc = db.getCollection("users");
}
public static void main(String[] args) {
insertData();
}
public static void insertData() {
Document userDocument = new Document();
userDocument.append(UsersMongoEnum.userName.name(), "田杰熠");
userDocument.append(UsersMongoEnum.age.name(), 18);
userDocument.append(UsersMongoEnum.country.name(), "长垣");
doc.insertOne(userDocument);
}
public static void findData() {
FindIterable<Document> documents = doc.find();
documents.forEach((Consumer<? super Document>) x -> System.out.println(x.toString()));
}
}
pom引用
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
添加.properties配置
spring.data.mongodb.uri=mongodb://192.168.227.131:27022/test
客户端的使用
//@Document(collection= "") , collection代表对应的集合名(表名)
//@Field(value= "") , value定义mongodb集合字段与pojo类字段的映射关系
package com.example.mongodbspring.bean;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@Document(collection = "users")
public class User {
private String id;
@Field(value = "user_name")
private String userName;
private int age;
private String country;
}
//mongoTemplate的简单使用
@RestController
public class MongodbController {
@Autowired
private MongoTemplate mongoTemplate;
@RequestMapping(value = "/test")
public void test() {
List<User> all = mongoTemplate.findAll(User.class);
all.forEach(x -> System.out.println(x.toString()));
}
}
查询选择器
属性 | 属性值 | 描述 |
---|---|---|
范围 | $eq | 等于 |
$lt | 小于 | |
$gt | 大于 | |
$lte | 小于等于 | |
$gte | 大于等于 | |
$in | 判断元素是否在指定的集合范围里 | |
$all | 判断数组中是否包含某几个元素,无关顺序 | |
$nin | 判断元素是否不在指定的集合范围里 | |
布尔运算 | $ne | 不等于,不匹配参数条件 |
$not | 不匹配结果 | |
$or | 有一个条件成立则匹配 | |
$nor | 所有条件都不匹配 | |
$and | 所有条件都必须匹配 | |
$exists | 判断元素是否存在 | |
其他 | $regex | 正则表达式匹配 |
查询喜欢的城市包含东莞和东京的用户
#mongo脚本
db.users.find({"heart_love.city":{"$all":["东莞","东京"]}}).pretty()
#原生java客户端
Bson all = all("heart_love.city", Arrays.asList("东莞", "东京"));
FindIterable<Document> documents = doc.find(all);
#springboot
Criteria all = where("heart_love.city").all(Arrays.asList("东莞", "东京"));
List<User> userList = mongoTemplate.find(query(all),User.class);
查询姓名为Jaye,WanCan,WangYaJie的用户
#mongo脚本
db.users.find({"userName":{"$in":["Jaye","WangCan","WangYaJie"]}}).pretty()
#原生java客户端
Bson in = in("userName", Arrays.asList("Jaye", "WangCan", "WangYaJie"));
FindIterable<Document> documents = doc.find(in);
#springboot
Criteria in = where("userName").in("Jaye", "WangCan", "WangYaJie");
List<User> userList = mongoTemplate.find(query(in),User.class);
判断文档有没有关心的字段
#mongo脚本
db.users.find({ "heart_love" : {"$exists" : true} }).pretty()
#原生java客户端
Bson exists = exists("heart_love", true);
FindIterable<Document> documents = doc.find(exists);
#springboot
Criteria exists = where("heart_love").exists(true);
List<User> userList = mongoTemplate.find(query(exists),User.class);
查询年龄小于18,或者没有年龄的人
#mongo脚本
db.users.find({"$or":[{"age":{"$lt":18}},{"age":{"$exists":false}}]}).pretty()
或者
db.users.find({"age":{"$not":{"$gte":18}}}).pretty()
#原生java客户端
Bson lt = lt("age", 18);
Bson exists = exists("age", false);
FindIterable<Document> documents = doc.find(or(lt, exists));
或者
Bson gte = gte("age", 18);
FindIterable<Document> documents = doc.find(not(gte));
#springboot
Criteria criteria = new Criteria();
criteria.orOperator(where("age").lt(18),where("age").exists(false));
List<User> userList = mongoTemplate.find(query(criteria), User.class);
或者
Criteria criteria = where("age").not().gte(18);
List<User> userList = mongoTemplate.find(query(criteria), User.class);
只查询名称和年龄两个字段
#mongo脚本
db.users.find({},{userName:1,age:1}).pretty()
#原生java客户端
Bson include = include("userName", "age");
FindIterable<Document> documents = doc.find().projection(include);
#springboot
Query query = query(new Criteria());
query.fields().include("userName").include("age");
List<User> userList = mongoTemplate.find(query, User.class);
排序
#mongo脚本
db.users.find().sort({age:1}).pretty() //ASC 正序
db.users.find().sort({age:-1}).pretty() //DESC 反序
#java原生客户端
Bson ascending = ascending("age"); //ASC 正序
Bson descending = descending("age"); //DESC 反序
FindIterable<Document> documents = doc.find().sort(ascending);
#Spring
Sort sortBy = Sort.by(Sort.Direction.ASC, "age");
Query query = query(new Criteria()).with(sortBy);
List<User> userList = mongoTemplate.find(query, User.class);
分页
#mongo脚本
skip : 从第几条数据开始查询
limit : 查询几条数
db.users.find().skip(1).limit(2).pretty()
#java原生客户端
FindIterable<Document> documents = doc.find().skip(1).limit(2);
#springboot
Query query = query(new Criteria()).skip(1).limit(2);
List<User> userList = mongoTemplate.find(query, User.class);
查询唯一值
#mongo脚本
db.users.distinct("user_name")
//查询结果
[ "张三", "李四", "" ]
#java原生客户端
DistinctIterable<Integer> ageDistinct = doc.distinct("age", Integer.class);
ageDistinct.forEach((Consumer<? super Integer>) x-> System.out.println( "age: " + x));
#springboot
List<Integer> ageList = mongoTemplate.findDistinct("age", User.class, Integer.class);
数组单元素查询(查询喜欢的电影包含西游记)
db.users.find({"heart_love.movies" : "西游记"}).pretty()
Bson eq = eq("heart_love.movies", "西游记");
FindIterable<Document> documents = doc.find(eq);
documents.forEach((Consumer<? super Document>) x -> System.out.println(x.toString()));
Criteria is = where("heart_love.movies").is("西游记");
List<User> userList = mongoTemplate.find(query(is), User.class);
数组精确查询(查询喜欢的电影等于西游记,一路向西 的用户. 顺序,数量完全一致 )
db.users.find({"heart_love.movies" : ["西游记","一路向西"]}).pretty()
Bson eqb = eq("heart_love.movies", Arrays.asList("西游记","一路向西"));
FindIterable<Document> documents = doc.find(eqb);
Criteria all = where("heart_love.movies").is(Arrays.asList("西游记","一路向西"));
List<User> userList = mongoTemplate.find(query(all), User.class);
索引查询(喜欢的电影第一个是东游记)
db.users.find({"heart_love.movies.0" : "东游记"}).pretty()
返回数组子集(查询)
db.users.find({},{"heart_love.movies":{"$slice":[1,1]}}).pretty();
查询喜欢的电影有东游记或者一路向西的用户
db.users.find({"heart_love.movies":{"$in":["东游记","一路向西"]}}).pretty();
查询喜欢的电影名称中含有苍老师字和喜欢的城市是东京的用户
db.users.find({"heart_love":{"$elemMatch":{"city":"东京","movies":{"$regex":".*苍老师.*"}}}}).pretty()
什么是聚合
聚合框架就是定义一个管道,管道里的每一步都为下一步输出数据
聚合常用的操作
$project:投影,指定输出文档中的字段;
$match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作
$limit:用来限制MongoDB聚合管道返回的文档数。
$skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
$unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
$group:将集合中的文档分组,可用于统计结果。(分组后的数据可执行如下的表达式计算)
$sum:计算总和。
$avg:计算平均值。
$min:根据分组,获取集合中所有文档对应值得最小值。
$max:根据分组,获取集合中所有文档对应值得最大值。
$sort:将输入文档排序后输出。
聚合操作实战
#查询2015年4月3号之前,每个用户每个月消费的总金额,并按用户名进行排序
db.orders.aggregate([{"$match":{"orderTime":{"$lt":new Date("2015-04-03T16:00:00.000Z")}}},{"$group":{"_id":{"useCode":"$useCode","month":{"$month":"$orderTime"}},"total":{"$sum":"$price"}}},{"$sort":{"_id":1}}]).pretty()
#查询2015年4月3号之前,每个审核员分别审批的订单总金额,按审核员名称进行排序
db.orders.aggregate([{"$match":{"orderTime":{"$lt":new Date("2015-04-03T16:00:00.000Z")}}},{"$unwind":"$Auditors"},{"$group":{"_id":{"Auditors":"$Auditors"},"total":{"$sum":"$price"}}},{"$sort":{"_id":1}}]).pretty()
语法
update() 方法用于更新已存在的文档。语法格式如下:
db.collection.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> } )
参数说明:
query : update的查询条件,类似sql update查询内where后面的;
update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入,true为插入,默认是false,不插入。
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern :可选,写策略配置。
更新操作符
属性 | 属性值 | 描述 |
---|---|---|
操作符 | $inc | 指定值加n |
$set | 更新指定字段 | |
$unset | 删除指定字段 | |
$rename | 更新字段名称 | |
数组操作符 | $push | 添加值到数组中 |
$addToSet | 添加到数组中,有重复则不添加 | |
$pop | 删除数组第一个或者最后一个 | |
$pull | 从数组中删除匹配查询条件 | |
$pullAll | 从数组中删除多个值 | |
数组运算修饰符 | $each | 与$push和$addToSet等一起使用来操作多个值 |
$slice | 与$push和$each一起使用来操作用来缩小更新后数组的大小 | |
$sort | 与$push 、$each 和 $slice 一起使用来对数组进行排序 |
删除用户名为jaye的 heart_love和 age 两个字段
db.users.updateMany({"user_name":"jaye"},{"$unset":{"heart_love":"","age":""}})
更新用户名为wang的 user_name字段为username 和 heart_love字段为heartlove
db.users.updateMany({"user_name":"wang"},{"$rename":{"heart_love":"heartlove","user_name":"username"}})
$each作用示例
不使用$each效果
db.users.updateMany({"user_name":"jaye"},{"$push":{"game":["古剑奇谭","仙剑"]}})
#mongodb脚本
db.users.updateMany({"user_name":"jaye"},{"$push":{"game":{"$each":["古剑奇谭","仙剑"]}}})
#java原生客户端
Bson q = eq("username", "jaye");
Bson u = pushEach("game", Arrays.asList("古剑奇谭3","仙剑6"));
doc.updateMany(q,u);
#spring
Criteria q = where("username").is("jaye");
Update u = new Update().push("game").each("古剑奇谭","仙剑");
mongoTemplate.updateMulti( query(q), u, User.class);
删除字符串数组中元素示例
#删除用户名为jaye的文档中 game字段中等于["古剑奇谭","仙剑"]的数据
db.users.updateMany({"user_name":"jaye"},{"$pull":{"game":["古剑奇谭","仙剑"]}})
#删除用户名为jaye的文档中 game字段中等于 "古剑奇谭" 或者 "仙剑" 的数据
db.users.updateMany({"user_name":"jaye"},{"$pullAll":{"game":["古剑奇谭","仙剑"]}})
对象数组更新示例
#为用户名为wang的文档添加一条喜好信息
db.users.updateMany({"username":"wang"},{"$push":{"heartlove":{"movies":"善良的小姨子","city":"东莞"}}})
#为用户名为wang的文档添加多条喜好信息
db.users.updateMany({"username":"wang"},{"$push":{"heartlove":{"$each":[{"movies":"前辈的女朋友","city":"首尔"},{"movies":"表妹","city":"东莞"}]}}})
#为用户名为wang的文档添加多条喜好信息,并进行排序
db.users.updateMany({"username":"wang"},{"$push":{"heartlove":{"$each":[{"movies":"前辈的女朋友","city":"首尔"},{"movies":"表妹","city":"东莞"}],"$sort":{"city":-1}}}})
#删除用户名为wang的文档中喜好城市为东莞的信息
db.users.updateMany({"username":"wang"},{"$pull":{"heartlove":{"city":"东莞"}}})
#删除用户名为wang的文档中喜好city为南京且movies为日本鬼子的信息
db.users.updateMany({"username":"wang"},{"$pull":{"heartlove":{"city":"南京","movies":"日本鬼子"}}})
#修改喜好的内容,如果多个符合条件的数据,则修改第一条数据。 无法批量修改
db.users.updateMany({"username":"wang","heartlove.city":"首尔"},{"$set":{"heartlove.$.movies":"表妹","heartlove.$.city":"东莞"}})
#为用户名为wang的文档age字段加1
db.users.updateMany({"username":"wang"},{"$inc":{"age":1}})
db.users.update() 更新一条数据
db.users.updateMany() 更新多条数据
db.users.findAndModify({query:{},update:{},‘new’:true }) 更新数据并返回更新前和更新后的数据
db.users.upsert() 有则更新,无则插入
删除脚本
deleteOne(query) //删除单个文档
deleteMany(query) //删除多个文档
## 删除操作是不会删除索引的,就算你把数据全部删除
字段说明
数名 | 默认值 | 说明 |
---|---|---|
minConnectionsPerHost | 最小连接数, Connections-Per-Host | |
font color=red> connectionsPerHost | 100 | 最大连接数 |
ont color=red> threadsAllowedToBlockForConnectionMultiplier | 5 | 此参数与 connectionsPerHost 的乘机为一个线程变为可用的最大阻塞数,所有线程将及时获取一个异常 |
maxWaitTime | 1000 * 60 * 2 | 一个线程等待连接可用的最大等待毫秒数,0表示不等待 |
onnectTimeout | 1000 * 10 | 连接超时时间 (建立连接时,超过该时间还没有建立成功则连接超时) |
axConnectionIdleTime | 0 | 设置池连接的最大空闲时间,0表示没有限制 |
axConnectionLifeTime | 0 | 设置池连接的最大使用时间,0表示没有限制 |
waysUseMBeans | false | 是否打开JMX监控 |
artbeatFrequency | 10000 | 设置心跳频率。 这是驱动程序尝试确定群集中每个服务器的当前状态的频率。 |
nHeartbeatFrequency | 500 | 设置最低心跳频率。 如果驱动程序必须经常重新检查服务器的可用性,那么至少要等上一次检查以避免浪费。 |
artbeatConnectTimeout | 20000 | 心跳检测连接超时时间 |
artbeatSocketTimeout | 20000 | 心跳检测Socket超时时间 |
decRegistry | MongoClient.getDefaultCodecRegistry() | 编解码类,实现Codec接口 |
iteConcern | ACKNOWLEDGED | 写入安全机制,是一种客户端设置,用于控制写入安全的级别: ACKNOWLEDGED 默认选项,数据写入到Primary就向客户端发送确认 0 Unacknowledged 对客户端的写入不需要发送任何确认,适用于性能要求高,但不关注正确性的场景; 1 W1 数据写入后,会等待集群中1台发送确认 2 W2 数据写入后,会等待集群中两台台发送确认 3 W3 数据写入后,会等待集群中3台台发送确认 JOURNALED 确保所有数据提交到 journal file MAJORITY 等待集群中大多数服务器提交后确认; |
mongoDB的存储引擎简介
MongoDB从3.0开始引入可插拔存储引擎的概念。目前主要有MMAPV1、WiredTiger存储引擎可供选择。在3.2版本之前MMAPV1是默认
的存储引擎,其采用linux操作系统内存映射技术,但一直饱受诟病;3.4以上版本默认的存储引擎是wiredTiger,相对于MMAPV1其有如下优势:
1、读写操作性能更好,WiredTiger能更好的发挥多核系统的处理能力;
2、MMAPV1引擎使用表级锁,当某个单表上有并发的操作,吞吐将受到限制。WiredTiger使用文档级锁,由此带来并发及吞吐的提高
3、相比MMAPV1存储索引时WiredTiger使用前缀压缩,更节省对内存空间的损耗;
4、提供压缩算法,可以大大降低对硬盘资源的消耗,节省约60%以上的硬盘资源;
mongDB ,写入策略
写策略配置:{ w: , j: , f:,wtimeout: }
w: 数据写入到number个节点才向用客户端确认
{w: 0} 对客户端的写入不需要发送任何确认,适用于性能要求高,但不关注正确性的场景
{w: 1} 默认的writeConcern,数据写入到Primary就向客户端发送确认
{w: “majority”} 数据写入到副本集大多数成员后向客户端发送确认,适用于对数据安全性要求比较高的场景,该选项会降低写入性能
j: 写入操作的journal持久化后才向客户端确认
默认为{j: false},如果要求写入持久化了才向客户端确认,则指定该选项为true
f: 写入操作等待服务器将数据刷新到磁盘,才向客户端确认
默认为{f: null},如果要求写入了磁盘才向客户端确认,则指定该选项为true
wtimeout: 写入超时时间,仅w的值大于1时有效。
当指定{w: }时,数据需要成功写入number个节点才算成功,如果写入过程中有节点故障,可能导致这个条件一直不能满足,从而一直不能向客户端发送确认结果,针对这种情况,客户端可设置wtimeout选项来指定超时时间,当写入过程持续超过该时间仍未结束,则认为写入失败。
java代码中写策略配置相关的类是 com.mongodb.WriteConcern
WriteConcern提供了以下的默认策略
UNACKNOWLEDGED:不等待服务器返回或确认,仅可以抛出网络异常;
ACKNOWLEDGED:默认配置,等待服务器返回结果;
JOURNALED:等待服务器完成journal持久化之后返回;
W1 :等待集群中一台服务器返回结果;
W2 :等待集群中两台服务器返回结果;
W3 :等待集群中三台服务器返回结果;
MAJORITY:等待集群中多数服务器返回结果
索引的创建语法
MongoDB使用 ensureIndex() 方法来创建索引
db.collection.createIndex(keys, options)
#单键索引
在某一个特定的属性上建立索引,例如:db.users. createIndex({age:-1});
mongoDB在ID上建立了唯一的单键索引,所以经常会使用id来进行查询;
在索引字段上进行精确匹配、排序以及范围查找都会使用此索引;
#复合索引
在多个特定的属性上建立索引,例如:db.users. createIndex({username:1,age:-1,country:1});
复合索引键的排序顺序,可以确定该索引是否可以支持排序操作;
在索引字段上进行精确匹配、排序以及范围查找都会使用此索引,但与索引的顺序有关;
为了性能考虑,应删除存在与第一个键相同的单键索引
#多键索引
在数组的属性上建立索引,例如:db.users. createIndex({favorites.city:1});针对这个数组的任意值的查询都会定位到这个文
档,既多个索引入口或者键值引用同一个文档
#哈希索引
不同于传统的B-树索引,哈希索引使用hash函数来创建索引。
例如:db.users. createIndex({username : 'hashed'});
在索引字段上进行精确匹配,但不支持范围查询,不支持多键hash;
Hash索引上的入口是均匀分布的,在分片集合中非常有用;
索引创建与删除实例
单键唯一索引:db.users. createIndex({username :1},{unique:true});
单键唯一稀疏索引:db.users. createIndex({username :1},{unique:true,sparse:true});
复合唯一稀疏索引:db.users. createIndex({username:1,age:-1},{unique:true,sparse:true});
创建哈希索引并后台运行:db.users. createIndex({username :'hashed'},{background:true});
根据索引名字删除某一个指定索引:db.users.dropIndex("username_1");
删除某集合上所有索引:db.users.dropIndexs();
重建某集合上所有索引:db.users.reIndex();
查询集合上所有索引:db.users.getIndexes();
查询优化
#开启慢查询 - n的取值可选0,1,2
#0是默认值表示不记录;
#1表示记录慢速操作,如果值为1,m必须赋值单位为ms,用于定义慢速查询时间的阈值;
#2表示记录所有的读写操作;
db.setProfilingLevel(n,{m})
#查询监控结果
#监控结果保存在一个特殊的盖子集合system.profile里.
#这个集合分配了128kb的空间,要确保监控分析数据不会消耗太多的系统性资源.
#盖子集合维护了自然的插入顺序,可以使用$natural操作符进行排序.
#盖子集合 Tips:
#1、大小或者数量固定;
#2、不能做update和delete操作;
#3、容量满了以后,按照时间顺序,新文档会覆盖旧文档
db.system.profile.find().sort({'$natural':-1}).limit(5)
#使用explain分析慢查询语句
#explain的入参可选值为:
#"queryPlanner" 是默认值,表示仅仅展示执行计划信息;
#"executionStats" 表示展示执行计划信息同时展示被选中的执行计划的执行情况信息;
#"allPlansExecution" 表示展示执行计划信息,并展示被选中的执行计划的执行情况信息,还展示备选的执行计划的执行情况信息;
db.orders.find({'price':{'$lt':2000}}).explain('executionStats')
#explain执行计划各个字段代表的含义
queryPlanner(执行计划描述)
winningPlan(被选中的执行计划)
stage(可选项:COLLSCAN 没有走索引;IXSCAN使用了索引)
rejectedPlans(候选的执行计划)
executionStats(执行情况描述)
nReturned (返回的文档个数)
executionTimeMillis(执行时间ms)
totalKeysExamined (检查的索引键值个数)
totalDocsExamined (检查的文档个数)
#优化目标
根据需求建立索引
每个查询都要使用索引以提高查询效率, winningPlan.stage 必须为IXSCAN
追求totalDocsExamined = nReturned
可复制集
可复制集是跨多个MongDB服务器(节点)分布和维护数据的方法。mongoDB可以把数据从一个节点复制到其他节点并在修改时进行同步,集群中的节点配置为自动同步数据;旧方法叫做主从复制,mongoDB 3.0以后推荐使用可复制集;
为什么要用可复制集?它有什么重要性?
1、避免数据丢失,保障数据安全,提高系统安全性;
(最少3节点,最大50节点)
2、自动化灾备机制,主节点宕机后通过选举产生新主机;提高系统健壮性;
(7个选举节点上限)
3、读写分离,负载均衡,提高系统性能;
4、生产环境推荐的部署模式;
oplog(操作日志):保存操作记录、时间戳
数据同步:从节点与主节点保持长轮询;1.从节点查询本机oplog最新时间戳;2.查询主节点oplog晚于此时间戳的所有文档;3.
加载这些文档,并根据log执行写操作;
阻塞复制:与writeconcern相关,不需要同步到从节点的策略
(acknowledged Unacknowledged 、w1)数据同步都是异步的
其他的策略情况都是同步;
心跳机制:成员之间会每2s 进行一次心跳检测(ping操作),发现故障后进行选举和故障转移;
选举制度:主节点故障后,其余节点根据优先级和bully算法选举出新的主节点,在选出主节点之前,集群服务是只读的;
可复制集的搭建
#1.安装好3个以上的MongoDB节点
#2.更新所有节点的配置文档 mongodb.conf,增加以下配置
replication:
replSetName: configRS #集群名称
oplogSizeMB: 50 #oplog集合大小
#3、在primary节点上运行可复制集的初始化命令,初始化可复制集.
use amdin #进入admin库
rs.initiate({
_id: "configRS",
version: 1,
members: [{ _id: 0, host : "192.168.1.142:27017" }]});
rs.add("192.168.1.142:27018"); #有几个节点就执行几次方法
rs.add("192.168.1.142:27019"); #有几个节点就执行几次方法
#4、查看可复制集状态
db.isMaster() 或 rs.status()
#5、在服务器上通过客户端 在副节点查询数据时都需要先运行
rs.slaveOk()
分片集群
为什么要用分片集群?
数据海量增长,需要更大的读写吞吐量 → 存储分布式
单台服务器内存、cpu等资源是有瓶颈的 → 负载分布式
Tips:分片集群是个双刃剑,在提高系统可扩展性和性能的同时,增大了系统的复杂性,所以在实施之前请确定是必须的。
MongoDB是怎么分片的?
mongoDB分片集群推荐的模式是:分片集合,它是一种基于分片键的逻辑对文档进行分组,分片键的选择对分片非常重要,分片键一旦确定,mongoDB对数据的分片对应用是透明的
#1、配置服务器配置文件
storage:
dbPath: "/usr/local/mongodb/mongodb-27022/data"
systemLog:
destination: file
path: "/usr/local/mongodb/mongodb-27022/logs/mongodb.log"
net:
port: 27022
http:
RESTInterfaceEnabled: true
processManagement:
fork: false
replication:
replSetName: configRS
oplogSizeMB: 50
sharding:
clusterRole: configsvr
#2、为配置服务器配置可复制集
rs.initiate({
_id: "configRS",
version: 1,
members: [{ _id: 0, host : "192.168.227.131:27022"}]});
rs.add("192.168.227.131:27023");
rs.add("192.168.227.131:27024");
#3、存储服务器配置
storage:
dbPath: "/usr/local/mongodb/mongodb-27026/data"
systemLog:
destination: file
path: "/usr/local/mongodb/mongodb-27026/logs/mongodb.log"
net:
port: 27026
http:
RESTInterfaceEnabled: true
processManagement:
fork: false
replication:
replSetName: dataRS
oplogSizeMB: 50
sharding:
clusterRole: shardsvr
#4、为存储服务器27026,27027,27028三台设置成可复制集
rs.initiate({
_id: "dataRS",
version: 1,
members: [{ _id: 0, host : "192.168.227.131:27026"}]});
rs.add("192.168.227.131:27027");
rs.add("192.168.227.131:27028");
#5.路由服务器配置(其中 configRS 为配置服务器的可复制集名称)
systemLog:
destination: file
path: "/usr/local/mongodb/mongodb-27025/logs/mongodb.log"
net:
port: 27025
processManagement:
fork: false
sharding:
configDB: configRS/192.168.227.131:27022,192.168.227.131:27023,192.168.227.131:27024
#6、登录路由服务器,添加分片服务器配置
use admin
#添加分片服务器
sh.addShard("192.168.227.131:27029");
sh.addShard("192.168.227.131:27030");
sh.addShard("dataRS/192.168.227.131:27026,192.168.227.131:27027,192.168.227.131:27028");
#为test库设置为具有可分片功能
sh.enableSharding("test")
#为test库的orders表 设置为以useCode自动进行分片
sh.shardCollection("test.orders",{"useCode":"hashed"});
注意:路由服务器的启动命令为 nohup ./mongos -f mongodb.conf &
使用场景
并没有某个业务场景必须要使用 MongoDB才能解决,但使用 MongoDB 通常能让你以更低的成本解决问题
(包括学习、开 发、运维等成本)
MongoDB 的应用已经渗透到各个领域,比如游戏、物流、电商、内容管理、社交、物联网、视频直播等,
1、游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新
2、物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就
能将订单所有的变更读取出来。
3、社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能
4、物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
5、视频直播,使用 MongoDB 存储用户信息、礼物信息等
什么场景不能用MongoDB?
1、高度事务性系统:例如银行、财务等系统。MongoDB对事物的支持较弱;
2、传统的商业智能应用:特定问题的数据分析,多数据实体关联,涉及到复杂的、高度优化的查询方式;
3、使用sql方便的时候;数据结构相对固定,使用sql进行查询统计更加便利的时候;
索引的使用建议
1、索引很有用,但是它也是有成本的——它占内存,让写入变慢;
2、mongoDB通常在一次查询里使用一个索引,所以多个字段的查询或者排序需要复合索引才能更加高效;
3、复合索引的顺序非常重要,例如此脚本所示:
4、在生成环境构建索引往往开销很大,时间也不可以接受,在数据量庞大之前尽量进行查询优化和构建索引;
5、避免昂贵的查询,使用查询分析器记录那些开销很大的查询便于问题排查;
6、通过减少扫描文档数量来优化查询,使用explain对开销大的查询进行分析并优化;
7、索引是用来查询小范围数据的,不适合使用索引的情况:
每次查询都需要返回大部分数据的文档,避免使用索引
写比读多
总体总结
1、尽量选取稳定新版本64位的mongodb;
2、数据模式设计;提倡单文档设计,将关联关系作为内嵌文档或者内嵌数组;当关联数据量较大时,考虑通过表关联实现,dbref或
者自定义实现关联;
3、避免使用skip跳过大量数据;(1)通过查询条件尽量缩小数据范围;(2)利用上一次的结果作为条件来查询下一页的结果;
4、避免单独使用不适用索引的查询符($ne、$nin、$where等)
5、根据业务场景,选择合适的写入策略,在数据安全和性能之间找到平衡点;
6、索引建议很重要;
7、生产环境中建议打开profile,便于优化系统性能;
8、生产环境中建议打开auth模式,保障系统安全;
9、不要将mongoDB和其他服务部署在同一台机器上(mongodb占用的最大内存是可以配置的);
10、单机一定要开启journal日志,数据量不太大的业务场景中,推荐多机器使用副本集,并开启读写分离;
11、分片键的注意事项
添加配置
spring.data.mongodb.server-addresses=192.168.227.131:27022
spring.data.mongodb.database=test
spring.data.mongodb.username=JayeTian
spring.data.mongodb.password=abc.123
spring.data.mongodb.min-connections-per-host=5
spring.data.mongodb.connections-per-host=100
spring.data.mongodb.threads-allowed-to-block-for-connection-multiplier=5
spring.data.mongodb.max-wait-time=120000
spring.data.mongodb.connect-time-out=10000
logging.level.org.springframework.data.mongodb.core=debug
添加配置类
package com.example.mongodbspring.config;
import com.example.mongodbspring.convert.BigDecimalToDecimal128Converter;
import com.example.mongodbspring.convert.Decimal128ToBigDecimalConverter;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.core.convert.*;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class MongodbConfig {
@Value("${spring.data.mongodb.server-addresses}")
private String serverAddr;
@Value("${spring.data.mongodb.database}")
private String dataName;
@Value("${spring.data.mongodb.username}")
private String userName;
@Value("${spring.data.mongodb.password}")
private String password;
@Value("${spring.data.mongodb.min-connections-per-host}")
private int minPoolSize;
@Value("${spring.data.mongodb.connections-per-host}")
private int maxPoolSize;
@Value("${spring.data.mongodb.threads-allowed-to-block-for-connection-multiplier}")
private int waitQueueMultiple;
@Value("${spring.data.mongodb.max-wait-time}")
private int waitQueueTimeOutMs;
@Value("${spring.data.mongodb.connect-time-out}")
private int connectTimeOutMs;
@Bean
public MongoClient mongoClient() {
String connectionString = "mongodb://" + userName + ":" + password + "@"
+ serverAddr + "/" + dataName
+ "?minpoolsize=" + minPoolSize
+ "&maxpoolsize=" + maxPoolSize
+ "&waitqueuemultiple=" + waitQueueMultiple
+ "&waitqueuetimeoutms=" + waitQueueTimeOutMs
+ "&connecttimeoutms=" + connectTimeOutMs
+ "&w=1&journal=true&wtimeoutms=5000" //写策略
+ "&readPreference=secondaryPreferred";
//readPreference=secondaryPreferred即可实现读写分离,读请求优先到Secondary节点
//从而实现读写分离的功能
return MongoClients.create(connectionString);
}
@Bean
public MongoDbFactory mongoDbFactory() {
return new SimpleMongoClientDbFactory(mongoClient(), dataName);
}
//添加自定义的转换器
@Bean
public MongoConverter mongoConverter() {
DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory());
List<Converter<?, ?>> converterList = new ArrayList<>();
converterList.add(new BigDecimalToDecimal128Converter());
converterList.add(new Decimal128ToBigDecimalConverter());
MongoCustomConversions conversions = new MongoCustomConversions(converterList);
MongoMappingContext mappingContext = new MongoMappingContext();
mappingContext.setSimpleTypeHolder(conversions.getSimpleTypeHolder());
mappingContext.afterPropertiesSet();
MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mappingContext);
converter.setCustomConversions(conversions);
converter.setCodecRegistryProvider(mongoDbFactory());
converter.afterPropertiesSet();
return converter;
}
@Bean
public MongoTemplate mongoTemplate() {
return new MongoTemplate(mongoDbFactory(), mongoConverter());
}
}
自定义的转换类
package com.example.mongodbspring.convert;
import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;
import java.math.BigDecimal;
public class BigDecimalToDecimal128Converter implements Converter<BigDecimal, Decimal128> {
@Override
public Decimal128 convert(BigDecimal source) {
return new Decimal128(source);
}
}
package com.example.mongodbspring.convert;
import org.bson.types.Decimal128;
import org.springframework.core.convert.converter.Converter;
import java.math.BigDecimal;
public class Decimal128ToBigDecimalConverter implements Converter<Decimal128, BigDecimal> {
@Override
public BigDecimal convert(Decimal128 source) {
return source.bigDecimalValue();
}
}