1、传统的关系型数据库 (比如 MySQL), 在数据操作的”三高”需求以及对应的 Web 2.0 网站需求面前, 会有”力不从心”的感觉
所谓的三高需求:
高并发, 高性能, 高可用, 简称三高
而 MongoDB 可以应对三高需求
2、具体的应用场景:
3、这些应用场景中, 数据操作方面的共同点有:
数据量大
那么我们什么时候选择 MongoDB 呢?
除了架构选型上, 除了上述三个特点之外, 还要考虑下面这些问题:
如果上述有1个符合, 可以考虑 MongoDB, 2个及以上的符合, 选择 MongoDB 绝不会后悔.
MongoDB是一个开源, 高性能, 无模式的文档型数据库, 当初的设计就是用于简化开发和方便扩展, 是NoSQL数据库产品中的一种.是最 像关系型数据库(MySQL)的非关系型数据库.
它支持的数据结构非常松散, 是一种类似于 JSON 的 格式叫BSON, 所以它既可以存储比较复杂的数据类型, 又相当的灵活.
MongoDB中的记录是一个文档, 它是一个由字段和值对(field:value)组成的数据结构.MongoDB文档类似于JSON对象, 即一个文档认 为就是一个对象.字段的数据类型是字符型, 它的值除了使用基本的一些类型外, 还可以包括其他文档, 普通数组和文档数组
1、运行容器
docker run --name mongodb \
-p 27017:27017 \
-v /mydata/mongodb/data:/data/db/mongo \
-d mongo
-e MONGO_INITDB_ROOT_USERNAME=root \
-e MONGO_INITDB_ROOT_PASSWORD=laptoy \
2、下载可视化 下载地址
3、连接
默认保留的数据库
mongo --username=root --password=laptoy # 连接show dbs/databases; use [dbname]; # 使用数据库,若不存在则创建
当使用 use articledb 的时候. articledb 其实存放在内存之中, 当 articledb 中存在一个 collection 之后, mongo 才会将这个数据库持久化到硬盘之中.
db.dropDatabase() // 删除当前库
db.createCollection(name)show collections/tablesdb.[collectionName].drop()
插入文档若不存在集合默认隐式创建集合
// 向集合中添加一个文档
db.comment.insertOne({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date(),"likenum":NumberInt(10),"state":null}
)db.comment.insertMany([{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);
捕捉批量插入异常
try{
db.comment.insertMany([{"_id":"1","articleid":"100001","content":"我们不应该把清晨浪费在手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江湖","createdatetime":new Date("2019-08-05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"},{"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019-08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"},{"_id":"3","articleid":"100001","content":"我一直喝凉开水,冬天夏天都喝。","userid":"1004","nickname":"杰克船长","createdatetime":new Date("2019-08-06T01:05:06.321Z"),"likenum":NumberInt(666),"state":"1"},{"_id":"4","articleid":"100001","content":"专家说不能空腹吃饭,影响健康。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T08:18:35.288Z"),"likenum":NumberInt(2000),"state":"1"},{"_id":"5","articleid":"100001","content":"研究表明,刚烧开的水千万不能喝,因为烫嘴。","userid":"1003","nickname":"凯撒","createdatetime":new Date("2019-08-06T11:01:02.521Z"),"likenum":NumberInt(3000),"state":"1"}
]);} catch(e){print(e);
}
1、查询
db.comment.find({参数},{投影})db.comment.find()db.comment.find({"userid":"1003"}) // 根据articleid查询
db.comment.findOne({"articleid":"xxx"}) // 根据articleid查询第一条
2、投影查询
db.comment.find({"articleid":"xxx"},{"articleid":1}) // 投影查询
db.comment.find({"articleid":"xxx"},{"articleid":1,_id:0}) // 投影查询
默认投影有_id
db.collection.update(query, update, options)
1、覆盖修改
如果我们想修改_id为1的记录,点赞量为1001,输入以下语句:
db.comment.update({_id:"1"},{likenum:NumberInt(1001)})
执行后,我们会发现,这条文档除了likenum字段其它字段都不见了
2、局部修改
db.comment.update({_id:"2"},{$set:{likenum:NumberInt(889)}})
3、批量修改
更新所有用户为 1003 的用户的昵称为 凯撒大帝
// 默认只修改第一条数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}})
// 修改所有符合条件的数据
db.comment.update({userid:"1003"},{$set:{nickname:"凯撒大帝"}},{multi:true})
4、列值增长的修改
如果我们想实现对某列值在原有值的基础上进行增加或减少,可以使用 $inc 运算符来实现。
需求:对3号数据的点赞数,每次递增1
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}},{multi:true}))
db.comment.remove(条件)db.comment.remove({_id:"1"})
1、统计查询
db.comment.count() // 统计所有记录db.comment.count({userid:"1003"}) // 按条件统计
2、limit()
db.comment.find().limit(2) // 查询前n条db.comment.find().limit(2).skip(2) // 跳过前n条 查询前n条
3、排序查询
sort() 方法对数据进行排序,sort() 方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而 -1 是用于降序排列
db.comment.find().sort({userid:-1,likenum:1})
1、正则表达式 查询
db.collection.find({field:/正则表达式/})db.comment.find({content:/开水/}) // 查询评论内容包含“开水”的所有文档db.comment.find({content:/^专家/}) // 查询以专家开头的
2、比较查询
db.集合名称.find({ "field" : { $gt: value }}) // 大于: field > value
db.集合名称.find({ "field" : { $lt: value }}) // 小于: field < value
db.集合名称.find({ "field" : { $gte: value }}) // 大于等于: field >= value
db.集合名称.find({ "field" : { $lte: value }}) // 小于等于: field <= value
db.集合名称.find({ "field" : { $ne: value }}) // 不等于: field != value
3、包含查询
包含使用 $in
操作符。 示例:查询评论的集合中userid字段包含1003或1004的文档
db.comment.find({userid:{$in:["1003","1004"]}})
4、条件连接查询
db.comment.find({$and:[{likenum:{$gte:NumberInt(700)}},{likenum:{$lt:NumberInt(2000)}}
]})
db.comment.find({$or:[{userid:"1003"},{likenum:{$lt:1000}}
]})
索引支持在MongoDB中高效地执行查询。如果没有索引,MongoDB必须执行全集合扫描,即扫描集合中的每个文档,以选择与查询语句匹配的文档。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
如果查询存在适当的索引,MongoDB可以使用该索引限制必须检查的文档数。
索引是特殊的数据结构,它以易于遍历的形式存储集合数据集的一小部分。索引存储特定字段或一组字段的值,按字段值排序。索引项的排序支持有效的相等匹配和基于范围的查询操作。此外,MongoDB还可以使用索引中的排序返回排序结果。
MongoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)
1、单字段索引
MongoDB支持在文档的单个字段上创建用户定义的升序/降序索引,称为单字段索引(Single Field Index)。
对于单个字段索引和排序操作,索引键的排序顺序(即升序或降序)并不重要,因为MongoDB可以在任何方向上遍历索引。
2、复合索引
MongoDB还支持多个字段的用户定义索引,即复合索引(Compound Index)。
复合索引中列出的字段顺序具有重要意义。例如,如果复合索引由 { userid: 1, score: -1 } 组成,则索引首先按userid正序排序,然后在每个userid的值内,再在按score倒序排序。
3、其他索引
地理空间索引(Geospatial Index)、文本索引(Text Indexes)、哈希索引(Hashed Indexes)。
为了支持对地理空间坐标数据的有效查询,MongoDB提供了两种特殊的索引:返回结果时使用平面几何的二维索引和返回结果时使用球面几何的二维球面索引。
MongoDB提供了一种文本索引类型,支持在集合中搜索字符串内容。这些文本索引不存储特定于语言的停止词(例如“the”、“a”、“or”),而将集合中的词作为词干,只存储根词。
为了支持基于散列的分片,MongoDB提供了散列索引类型,它对字段值的散列进行索引。这些索引在其范围内的值分布更加随机,但只支持相等匹配,不支持基于范围的查询。
1、索引的查看
db.collection.getIndexes()
提示:该语法命令运行要求是MongoDB 3.0+
结果中显示的是默认_id 索引:
MongoDB在创建集合的过程中,在 id字段上创建一个唯一的索引,默认名字为_id,该索引可防止客户端插入两个具有相同值的文档,您不能在_id字段上删除此索引。
注意:该索引是唯一索引,因此值不能重复,即_id值不能重复的。在分片集群中,通常使用_id 作为片键。
2、创建索引
db.collection.createIndex(keys,options)
单字段索引
db.comment.createIndex({userid:-1})
复合索引
db.comment.createIndex({userid:-1,nickname:-1})
3、删除索引
db.collection.dropIndex(index)
删除 comment 集合中 userid 字段上的升序索引:
db.comment.dropIndex({userid:-1})
删除所有索引
db.comment.dropIndexes()
1、执行计划
分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是否基于索引查询等。
那么,通常,我们想知道,建立的索引是否有效,效果如何,都需要通过执行计划查看。
语法:
db.collection.find(query,options).explain(options)
db.comment.find({userid:"1003"}).explain()
预先创建索引并通过索引查询的结果 2选
没有预先创建索引的结果 5选
当查询条件和查询的投影仅包含索引字段时,MongoDB直接从索引返回结果(不再去找集合),而不扫描任何文档或将文档带入内存。 这些覆盖的查询可以非常有效。
db.comment.find({userid:"1003"},{userid:1,_id:0})
某头条的文章评论业务如下:
文章示例参考:早晨空腹喝水,是对还是错?https://www.toutiao.com/a6721476546088927748/
需要实现以下功能:
数据库:articledb
专栏文章评论 | comment | ||
---|---|---|---|
字段名称 | 字段含义 | 字段类型 | 备注 |
_id | ID | ObjectId或String | Mongo的主键字段 |
articleid | 文章ID | String | |
content | 评论内容 | String | |
userid | 评论人ID | String | |
nickname | 评论人昵称 | String | |
createdatetime | 评论的日期时间 | Data | |
likenum | 点赞数 | Int32 | |
replynum | 回复数 | Int32 | |
state | 状态 | String | 0:不可见;1:可见; |
parentid | 上级ID | String | 如果为0表示文章的顶级评论 |
1. mongodb-driver(了解)
mongodb-driver是mongo官方推出的java连接mongoDB的驱动包,相当于JDBC驱动。我们通过一个入门的案例来了解mongodb-driver的基本使用。
官方驱动说明和下载:http://mongodb.github.io/mongo-java-driver/
官方驱动示例文档:http://mongodb.github.io/mongo-java-driver/3.8/driver/getting-started/quick-start/
2. SpringDataMongoDB
SpringData家族成员之一,用于操作MongoDB的持久层框架,封装了底层的mongodb-driver。
官网主页: https://projects.spring.io/spring-data-mongodb/
我们十次方项目的吐槽微服务就采用SpringDataMongoDB框架
1、搭建项目工程article,pom.xml引入依赖:
org.springframework.boot spring-boot-starter-data-mongodb
org.projectlombok lombok
2、YML
spring:data:mongodb:host: 120.76.55.xxport: 27017database: articledb
1、实体类
@Data
@Document(collection = "comment")
public class Comment implements Serializable {@Idprivate String id;@Field("content") // 该属性对应mongodb的字段的名字,如果一致,则无需该注解private String content; // 吐槽内容private Date publishtime; // 发布日期@Indexedprivate String userid; // 发布人IDprivate String nickname; // 昵称private LocalDateTime createdatetime; // 评论的日期时间private Integer likenum; // 点赞数private Integer replynum; // 回复数private String state; // 状态private String parentid; // 上级IDprivate String articleid;
}
2、mapper
public interface CommentMapper extends MongoRepository {
}
3、service
@Service
public class CommentService {@AutowiredCommentMapper commentMapper;public void saveComment(Comment comment) {// 如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB会自动生成主键commentMapper.save(comment);}public void updateComment(Comment comment) {commentMapper.save(comment);}public void deleteCommentById(String id) {commentMapper.deleteById(id);}public List findCommentList() {return commentMapper.findAll();}public Comment findCommentById(String id) {return commentMapper.findById(id).get();}
}
4、测试
@SpringBootTest
class ArticleApplicationTests {@AutowiredCommentService commentService;@Testpublic void testFindCommentById() {Comment commentById = commentService.findCommentById("1");System.out.println(commentById);}@Testpublic void testSaveComment() {Comment comment = new Comment(); // 不设置id默认雪花算法生成comment.setArticleid("100000");comment.setContent("测试添加的数据");comment.setCreatedatetime(LocalDateTime.now());comment.setUserid("1006");comment.setNickname("Laptoy");comment.setState("1");comment.setReplynum(0);comment.setReplynum(0);commentService.saveComment(comment);}@Testpublic void testFindCommentList() {System.out.println(commentService.findCommentList());}
}
1、mapper新增
//根据父id,查询子评论的分页列表
Page findByParentid(String parentid, Pageable pageable);
2、service新增
public Page findCommentListByParentid(String parentid,int page,int size){return commentRepository.findByParentid(parentid, PageRequest.of(page-1,size));
}
3、测试
@Test
public void testFindCommentListByParentid(){Page pageResponse = commentService.findCommentListByParentid("3", 1, 2);System.out.println("---总记录数---:"+pageResponse.getTotalElements());System.out.println("---当前页数据---:"+pageResponse.getContent());
}
4、使用compass快速插入一条测试数据,数据的内容是对3号评论内容进行评论。
5、执行测试结果
---总记录数---:1
---当前页数据---:[Comment(id=62b86e74258c6f1903c71820, content=:不要让惰性毁了你, publishtime=null, userid=1003, nickname=null, createdatetime=null, likenum=null, replynum=null, state=null, parentid=3, articleid=100000)]
/*** 点赞-效率低* @param id*/
public void updateCommentThumbupToIncrementingOld(String id){Comment comment = commentRepository.findById(id).get();comment.setLikenum(comment.getReplynum()+1);commentRepository.save(comment);
}
以上方法虽然实现起来比较简单,但是执行效率并不高,因为我只需要将点赞数加1就可以了,没必要查询出所有字段修改后再更新所有字段。(蝴蝶效应)
我们可以使用MongoTemplate类来实现对某列的操作
db.comment.update({_id:"3"},{$inc:{likenum:NumberInt(1)}})
1、service
@Autowired
private MongoTemplate mongoTemplate;public void updataCommentLikenum(String id){//查询条件Query query = Query.query(Criteria.where("_id").is(id));//更新条件Update update = new Update();// 局部更新,相当于$set// update.set(key,value)// 递增$inc// update.inc("likenum",1);update.inc("likenum");//参数1:查询对象//参数2:更新对象//参数3:集合的名字或实体类的类型Comment.classmongoTemplate.updateFirst(query,update,Comment.class);
}
2、测试
@Test
public void testupdataCommentLikenum() {commentService.updataCommentLikenum("3");
}
MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高 可用性,是所有生产部署的基础。
也可以说,副本集类似于有自动故障恢复功能的主从集群。通俗的讲就是用多台机器进行同一数据的异 步同步,从而使多台机器拥有同一数据的多个副本,并且当主库当掉时在不需要用户干预的情况下自动 切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。
复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别 的容错功能,以防止丢失单个数据库服务器。
在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上, 在不 同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。 您还可以为专用目的维护其他 副本,例如灾难恢复,报告或备份。
副本集是一组维护相同数据集的mongod实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。 在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。
主节点接收所有写操作。 副本集只能有一个主要能够确认具有{w:“most”}写入关注的写入; 虽然在某 些情况下,另一个mongod实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有 更改,即oplog。
辅助(副本)节点复制主节点的oplog并将操作应用于其数据集,以使辅助节点的数据集反映主节点的数据 集。 如果主要人员不在,则符合条件的中学将举行选举以选出新的主要人员。
主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其挂 掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(主、primary)和一个或多 个备份节点(从、secondary)。
两种类型:
三种角色:
主要成员(Primary):主要接收所有写操作。就是主节点。
副本成员(Replicate):从主节点通过复制操作以维护相同的数据集,即备份数据,不可写操作,但可 以读操作(但需要配置)。是默认的一种从节点类型。
仲裁者(Arbiter):不保留任何数据的副本,只具有投票选举作用。当然也可以将仲裁服务器维护为副 本集的一部分,即副本成员同时也可以是仲裁者。也是一种从节点类型。
关于仲裁者的额外说明:
您可以将额外的mongod实例添加到副本集作为仲裁者。 仲裁者不维护数据集。 仲裁者的目的是通过 响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁。 因为它们不存储数据集,所以仲裁器可 以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。
如果您的副本集具有偶数个成员,请添加仲裁者以获得主要选举中的“大多数”投票。 仲裁者不需要专用 硬件。
仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要 人员。
如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。
如果你的副本+主节点的个数是奇数,可以不加仲裁者
一主一副本一仲裁
建立存放数据和日志的目录
mkdir -p /mydata/mongodb/replica_sets/myrs_27017/log
mkdir -p /mydata/mongodb/replica_sets/myrs_27017/data/dbvim /mydata/mongodb/replica_sets/myrs_27017/mongod.conf
systemLog:#MongoDB发送所有日志输出的目标指定为文件destination: file#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径path: "/mongodb/replica_sets/myrs_27017/log/mongod.log"#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。logAppend: true
storage:#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。dbPath: "/mongodb/replica_sets/myrs_27017/data/db"journal:#启用或禁用持久性日志以确保数据文件保持有效和可恢复。enabled: true
processManagement:#启用在后台运行mongos或mongod进程的守护进程模式。fork: true#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PIDpidFilePath: "/mongodb/replica_sets/myrs_27017/log/mongod.pid"
net:#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip#bindIpAll: true#服务实例绑定的IPbindIp: localhost,172.24.18.186#bindIp#绑定的端口port: 27017
replication:#副本集的名称replSetName: myrs