MongoDB基本使用
Nosql简介
在现代的计算系统上每天网络上都会产生庞大的数据量, 这些数据有很大一部分是由关系数据库管 理系统(RDBMS)来处理。 1970年 E.F.Codd’s提出的关系模型的论文 “A relational model of data for large shared data banks”,这使得数据建模和应用程序编程更加简单。 <br /> 通过应用实践证明,关系模型是非常适合于客户服务器编程,远远超出预期的利益,今天它是结构 化数据存储在网络和商务应用的主导技术。<br /> NoSQL 是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨。NoSQL 的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种 全新的思维的注入。
什么是是NoSql
NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的 关系型数据库的数据库管理系统的统称。<br /> NoSQL用于超大规模数据的存储。(例如谷歌或Facebook每天为他们的用户收集万亿比特的数 据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。
为什么使用NoSql
今天我们可以通过第三方平台(如:Google,Facebook等)可以很容易的访问和抓取数据。用户的 个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些 用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL 数据库的发展却能很好的处理这些大 的数据。
RDBMS vs NoSQL
RDBMS
- 高度组织化结构化数据
- 结构化查询语言(SQL)
- 数据和关系都存储在单独的表中。
- 数据操纵语言,数据定义语言
- 严格的一致性
- 基础事务
NOSql
- 代表着不仅仅是SQL
- 没有声明性查询语言
- 没有预定义的模式 键 - 值对存储,列存储,文档存储,图形数据库
- 最终一致性,而非ACID(原子性、一致性、隔离性、持久性)属性
- 非结构化和不可预知的数据
NoSql优缺点
优点
- 高可扩展性
- 分布式计算
- 低成本
- 架构的灵活性,半结构化数据
- 没有复杂的关系
缺点
- 没有标准化
- 有限的查询功能(到目前为止)
- 最终一致是不直观的程序
分布式理论
CAP定理
C(一致性):所有节点上数据时刻保持同步
A(可用性):每个请求都能得到响应,无论成功或失败
P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需 求,最多只能同时较好的满足两个。
因此,根据 CAP 原理将 NoSQL 数据库分成了满足 CA 原则、满足 CP 原则和满足 AP 原则三 大 类:
- CA - 单点集群,满足一致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍性的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
BASE理论
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需 求,最多只能同时较好的满足两个。
BASE是NoSQL数据库对可用性及一致性的弱要求原则:
- Basically Availble –基本可用
- Soft-state – 软状态/柔性事务。 Soft state” 可以理解为”无连接”的, 而 “Hard state” 是”面向连接” 的 软状态是指允许系统存在中间状态,并且该中间状态不会影响系统整体可用性。即允许系统在不同 节点间副本同步的时候存在延时。简单来说就是状态可以在一段时间内不同步。
- Eventual Consistency – 最终一致性, 也是 ACID 的最终目的。
MongoDB 基础
什么是MongoDB
MongoDB 是由C++语言编写的,是一个基于分布式文挡存储的开源数据库系统。
- 在高负载的情况下,添加更多的节点,可以保证服务器性能。
- MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。
存储结构
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
主要特点
- 非关系型数据库,基于 Document data model (文档数据模型)
- MongoDB以 BSON (BinaryJSON) 格式存储数据,类似于 JSON 数据形式
- 关系型数据库使用 table (tables of rows)形式存储数据,而MongoDB使用 collections (collections of documents)
- 支持 临时查询( ad hoc queries ): 系统不用提前定义可以接收的查询类型
- 索引通过 B-tree 数据结构, 3.2版本的WiredTiger 支持 log-structured merge-trees(LSM)
- 支持索引和次级索引( secondary indexes ): 次级索引是指文档或row有一个 主键( primary key )作为索引,同时允许文档或row内部还拥有一个索引,提升查询的效率,这也是MongoDB比较大的一个特点
基本概念
和传统数据库相比
对比项 | mongo | 数据库 |
---|---|---|
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 数据字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设置为主键 |
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oFGahbfA-1689774476280)(https://cdn.nlark.com/yuque/0/2023/png/26194198/1687786037504-c04ccddf-82dc-447f-ab33-fd80e1838a96.png#averageHue=%23d4d8c5&clientId=u5db58dc5-9299-4&from=paste&height=340&id=ua6d15fe2&originHeight=340&originWidth=1196&originalType=binary&ratio=1&rotation=0&showTitle=false&size=89504&status=done&style=none&taskId=u53132599-5840-4ae3-b927-551137dd160&title=&width=1196)]
数据逻辑层次关系:文档=>集合=>数据库\
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02CKGvev-1689774476282)(https://cdn.nlark.com/yuque/0/2023/png/26194198/1687786078353-41daa3cb-3411-41d9-952e-ad63b1df3974.png#averageHue=%23eff3f3&clientId=u5db58dc5-9299-4&from=paste&height=382&id=ua8df788a&originHeight=382&originWidth=421&originalType=binary&ratio=1&rotation=0&showTitle=false&size=122140&status=done&style=none&taskId=u8fc361bd-e135-4890-903e-66aca86f2fa&title=&width=421)]
下面我们对里面的每一个概念进行详细解释
数据库
一个mongoDB的实例可以运行多个database,database之间是完全独立的,每个database有自己的权限,每个database存储于磁盘的不同文件。
命名规范
- 空字符串””是非法的
- 不允许出现’’,.,$,/,,\0字符
- 建议名称都是小写
- 不能超过64个字节
特殊数据库
有一些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
- admin:它是root级别的数据库,如果一个用户创建了admin数据库,该用户将自动集成所有数据 库的权限,它可以执行一些服务器级别的命令,如列出所有数据库、关闭服务等。
- local:该数据库将永远不能被复制,只能在单台服务器本地使用。
- config:存储分布式部署时shard的配置信息
数据库操作
- show dbs 查看数据库列表
- db 显示当前数据库
- 创建数据库
show dbs;
#创建tmpdb数据库
use tmpdb;
show dbs;
注意:在 MongoDB 中,只有在数据库中插入集合后才会创建! 就是说,创建数据库后要再插入一 个集合,数据库才会真正创建。
- 删除数据库 可以使用 db.dropDatabase() 删除数据库
show dbs;
use tmpdb;
db;
#删除数据库
db.dropDatabase();
show dbs;
集合
相当于关系数据库的表,不过没有数据结构的定义。它由多个document组成。
命令规范
因为是无结构定义的,所以你可以把任何document存入一个collection里。每个collection用一个 名字标识,需要注意以下几点:
- 名字不允许是空字符串""
- 名字不能包含\0字符,因为它表示名字的结束
- 不能创建以system.开头的
集合操作
创建集合
可以通过 db.createCollection(name,option) 创建集合
参数说明:
name: 要创建的集合名称
options: 可选参数, 指定有关内存大小及索引的选项
# 创建或选择tmpdb数据库
use tmpdb;
# 在db数据库创建一个blog的集合
db.createCollection("blog");
查看集合
show collections;
show tables;
删除集合
MongoDB 中使用 drop() 方法来删除集合 db.collection.drop()
文档
mongoDB的基本单位,相当于关系数据库中的行,它是一组有序的key/value键值对,使用json 格式, 如:{“foo” : 3, “greeting”: “Hello, world!”}。
key的命令规范
key是个UTF-8字符串,以下几点是需要注意的地方:
- 不能包含\0字符(null字符),它用于标识key的结束 .
- 和 字符在 m a n g o d b 中有特殊含义,如 字符在mangodb中有特殊含义,如 字符在mangodb中有特殊含义,如被用于修饰符($inc表示更新修饰符),应该考虑保留,以 免被驱动解析
- 以_开始的key也应该保留,比如_id是mangodb中的关键字
注意事项
- 在mangodb中key是不能重复的
- value 是弱类型,甚至可以嵌入一个document
- key/value键值对在mangodb中是有序的
- mangodb是类型和大小写敏感的,如{“foo” : 3}和{“foo” : “3”}是两个不同的document,{“foo” : 3}和{“Foo” : 3}
文档基础使用
插入文档
insert(不推荐)
插入一条或多条数据需要带有允许插入多条的参数,这个方法目前官方已经不推荐了 注意:若插入的数据主键已经存在,则会抛 org.springframework.dao.DuplicateKeyException 异常,提示主键重复,不保存当前数据。
db.blog.insert({
"title": "MongoDB 教程",
"description": "MongoDB 是一个 Nosql 数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"mongodb",
"database",
"NoSQL"
],
"likes": 100
});
如果没有添加 _id 参数会自动生成 _id 值的,也可以自定义指定 _id
insertOne(推荐)
官方推荐的写法,向文档中写入一个文档
db.blog.insertOne({
"title": "MySql 教程",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 10000
});
insertMany(推荐)
该语句是进行批量插入的,可以直接进行批量插入
db.blog.insertMany([
{
"title": "MySql 教程1",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 10000
},
{
"title": "MySql 教程2",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 10000
}
]);
查询文档
查询所有文档
find 方法用于查询已存在的文档,MongoDB 查询数据的语法格式如下
db.blog.find();
格式化文档
db.blog.find().pretty();
等值查询
我们查询 blog 表中 title=‘MySql 教程2’ 的数据
db.blog.find({
"title": "MySql 教程2"
}).pretty();
投影
projection 选择可以控制某一列是否显示,语法格式如下 find({},{“title”:1}) 其中如果 title 是 1 则该列显示,否则不显示
// 只显示title列的数据
db.blog.find({"title":"MySql 教程2"},{"title":1}).pretty();
// 只显示title和description列的数据
db.blog.find({"title":"MySql 教程2"},{"title":1,"description":1}).pretty();
// 不显示 title和description列的数据
db.blog.find({"title":"MySql 教程2"},{"title":0,"description":0}).pretty();
更新文档
update() 方法用于更新已存在的文档,更新的时候需要加上关键字 $set
db.blog.find({"_id":"1"});
db.blog.update({"_id":"1"},{$set:{"likes":666}})
save更新
save() 方法通过传入的文档来替换已有文档,_id 主键存在就更新,不存在就插入
db.blog.save({
"_id": "1",
"title": "MySql 传统教程教程3",
"description": "Mysql是一个传统数据库",
"by": "我的博客",
"url": "http://www.baiyp.ren",
"tags": [
"Mysql",
"database"
],
"likes": 100000
});
删除文档
条件删除
remove() 方法可以删除文档 db.blog.remove({"_id":"1"})
delete删除文档
官方推荐使用 deleteOne() 和 deleteMany() 方法删除文档
删除单个文档
deleteOne 只会删除符合条件的第一个文档,和 remove({},true) 效果一致 db.blog.deleteOne({});
批量删除文档
deleteMany 可以进行批量删除文档,和 remove({}) 效果一致
db.blog.deleteMany({});
关系表达式
操作 | 格式 | 范例 | RDBMS中的类似 语句 |
---|---|---|---|
等于 | {key:value } | db.col.find({"by":"作者名称"}).pretty() | where by = '作 者名称' |
小于 | {key:{$lt:value }} | db.col.find({"likes": {$lt:50}}).pretty() | where likes < 50 |
小于或 等于 | {key:{$lte:value }} | ||
大于 | {key:{$gt:value }} | ||
大于或 等于 | {key:{$gte:value }} | ||
不等于 | {key:{$ne:value }} | ||
包含 | {key:{$in:value }} | ||
不包含 | {key:{$nin:value }} | ||
判断字段存在 | {key:{“$exists” :true }} | ||
多条件查询 ( 有时候存在一个字段需要多个条件,比如 pop>=10 and pop<50 这个如何表示呢 ) | {key:{“ g t e " : 10 , " gte" :10," gte":10,"lte”:100 }} | db.zips.find({ "pop": { "$gte": 10, "$lt": 50 } }).pretty(); | |
逻辑表达式
操作 | 格式 | 例子 |
---|---|---|
AND | {key:value,key:value} | db.zips.find({ “state”: “NY”, “pop”: { “$gt”: 100000 } }) |
OR | “KaTeX parse error: Expected '}', got 'EOF' at end of input: …ey": {<br /> "lt”: 0 } }] | db.zips.find({ “KaTeX parse error: Expected '}', got 'EOF' at end of input: …p": {<br /> "lt”: 0 } }] }) |
多条件表达式
db.zips.find({
"$or": [
{
"$and": [
{
"state": "NY"
},
{
"pop": {
"$gt": 10,
"$lte": 50
}
}
]
},
{
"$and": [
{
"state": {
"$in": [
"MD",
"VA"
]
}
},
{
"pop": {
"$gt": 10,
"$lte": 50
}
}
]
}
]
}).pretty();
### 对应sql
select * from zips where (state='NY' and pop>10 and pop <= 50) or (state
in('MD','VA') and pop>10 and pop <= 50)
排序
在MongoDB中使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。
语法格式:db.COLLECTION_NAME.find().sort({KEY1:1,KEY2:-1,....})
分页查询
MongoDB的分页
MongoDB提供了skip()和limit()方法。
- skip: 跳过指定数量的数据. 可以用来跳过当前页之前的数据,即跳过pageSize*(n-1)。
- limit: 指定从MongoDB中读取的记录条数,可以当做页面大小pageSize。
// 第一页数据
db.zips.find({},{"_id":1}).skip(0).limit(10);
// 第二页数据
db.zips.find({},{"_id":1}).skip(10).limit(10);
// 第三页页数据
db.zips.find({},{"_id":1}).skip(20).limit(10);
遇到的问题 看起来,分页已经实现了,但是官方文档并不推荐,说会扫描全部文档,然后再返回结果。
正确的分页办法
我们假设基于_id的条件进行查询比较,事实上,这个比较的基准字段可以是任何你想要的有序的 字段,比如时间戳 实现步骤如下 1. 对数据针对于基准字段排序 2. 查找第一页的最后一条数据的基准字段的数据 3. 查找超过基准字段数据然后向前找pagesize条数据
// 第一页数据
db.zips.find({},{_id:1}).sort({"_id":1}).limit(10);
// 第二页数据
db.zips.find({"_id":{$gt:"01020"}},{_id:1}).sort({"_id":1}).limit(10);
// 第三页数据
db.zips.find({"_id":{$gt:"01035"}},{_id:1}).sort({"_id":1}).limit(10);
ObjectId有序性
ObjectId生成规则
比如 “_id” : ObjectId(“5b1886f8965c44c78540a4fc”)
Objectid = 时间戳(4字节) + 机器(3个字节)+ PID(2个字节)+ 计数器(3个字节)
取id的前4个字节。由于id是16进制的string,4个字节就是32位,1个字节是两个字符,4个字节对应 id前8个字符。即 5b1886f8 , 转换成10进制为 1528334072 . 加上1970,就是当前时间。
ObjectId存在的问题
MongoDB的ObjectId应该是随着时间而增加的,即后插入的id会比之前的大。但考量id的生成规 则,最小时间排序区分是秒,同一秒内的排序无法保证。当然,如果是同一台机器的同一个进程生 成的对象,是有序的。
如果是分布式机器,不同机器时钟同步和偏移的问题。所以,如果你有个字段可以保证是有序的, 那么用这个字段来排序是最好的。 _id 则是最后的备选方案,可以考虑增加 雪花算法ID作为排序ID
统计查询
count
db.zips.find({
"pop": {
"$not": {
"$gte": 10
}
}
}).count();
distinct
无条件排重
db.zips.distinct("state");
有条件排重
db.zips.distinct("state", {
"pop": {
"$gt": 70000
}
});
SpringBoot整合Mongo
引入Pom坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
编写配置文件
server:
port: 8080
spring:
application:
name: spring-boot-test
data:
mongodb:
database: test
host: 192.168.10.30
port: 27017
定义实体类
Blog类
@Document("blog")
public class Blog {
@Id
private String id;
private String title;
private String by;
private String url;
private List<String> tags;
private int likes;
setter getter ....
}
Dao
@Component
public class BlogDao {
@Autowired
private MongoTemplate mongoTemplate;
public void insert(Blog blog) {
mongoTemplate.insert(blog);
}
public Blog findByID(String id) {
return mongoTemplate.findById(id, Blog.class);
}
public void deleteByID(String id) {
mongoTemplate.remove(Query.query(Criteria.where("_id").is(id)),
Blog.class);
}
public List<Blog> find(Blog blog) {
if (null == blog) {
return null;
}
Criteria criteria = getFilter(blog);
return mongoTemplate.find(Query.query(criteria), Blog.class);
}
public Criteria getFilter(Blog blog) {
Criteria criteria = new Criteria();
if (!StringUtils.isEmpty(blog.getTitle())) {
criteria.andOperator(Criteria.where("title").is(blog.getUrl()));
}
if (!StringUtils.isEmpty(blog.getBy())) {
criteria.andOperator(Criteria.where("by").is(blog.getBy()));
}
if (!StringUtils.isEmpty(blog.getLikes())) {
criteria.andOperator(Criteria.where("likes").is(blog.getLikes()));
}
if (null != blog.getTags() && !blog.getTags().isEmpty()) {
criteria.andOperator(Criteria.where("tags").in(blog.getTags()));
}
return criteria;
}
}
Controller
@RestController
@RequestMapping("/blog")
public class WebController {
@Resource
private BlogDao blogDao;
@RequestMapping("/{id}")
@ResponseBody
public String getBlogInfo(@PathVariable("id") String id) {
Blog blog = blogDao.findByID(id);
if (null == blog) {
return "访问的数据不存在";
}
return JSON.toJSONString(blog);
}
@RequestMapping("/add")
@ResponseBody
public String addBlog(@RequestBody Blog blog) {
blogDao.insert(blog);
return JSON.toJSONString(blog);
}
public void batchAdd(){
}
}