0%

MongoDB的那点事

  1. SQL(关系型数据库)与 NOSQL(非关系型数据库)的区别
    1. 概念

SQL (Structured Query Language) 数据库,指关系型数据库 - 主要代表:SQL Server,Oracle,MySQL(开源),PostgreSQL(开源)。

NoSQL(Not Only SQL)泛指非关系型数据库 - 主要代表:MongoDB,Redis,CouchDB。

  1. 主要区别
    1. 存储方式

SQL 数据存在特定结构的表中;而 NoSQL 则更加灵活和可扩展,存储方式可以是 JSON 文档、哈希表或者其他方式。

SQL 通常以数据库表形式存储数据。举个栗子,存个学生借书数据:

图片

而 NoSQL 存储方式比较灵活,比如使用类 JSON 文件存储上表中熊大的借阅数据:

图片

2. **表/数据集合的数据关系**

在 SQL 中,必须定义好表和字段结构后才能添加数据,例如定义表的主键(primary key),索引(index),触发器(trigger),存储过程(stored procedure)等。表结构可以在被定义之后更新,但是如果有比较大的结构变更的话就会变得比较复杂。

在 NoSQL 中,数据可以在任何时候任何地方添加,不需要先定义表。例如下面这段代码会自动创建一个新的”借阅表”数据集合:

图片

NoSQL 也可以在数据集中建立索引。以 MongoDB 为例,会自动在数据集合创建后创建唯一值_id 字段,这样的话就可以在数据集创建后增加索引。

从这点来看,NoSQL 可能更加适合初始化数据还不明确或者未定的项目中。

3. **外部数据存储**

SQL 中如何需要增加外部关联数据的话,规范化做法是在原表中增加一个外键,关联外部数据表。例如需要在借阅表中增加审核人信息,先建立一个审核人表

图片

再在原来的借阅人表中增加审核人外键

图片

这样如果我们需要更新审核人个人信息的时候只需要更新审核人表而不需要对借阅人表做更新。

而在 NoSQL 中除了这种规范化的外部数据表做法以外,我们还能用如下的非规范化方式把外部数据直接放到原数据集中,以提高查询效率。缺点也比较明显,更新审核人数据的时候将会比较麻烦。

图片

4. **SQL 中的 JOIN 查询**

SQL 中可以使用 JOIN 表链接方式将多个关系数据表中的数据用一条简单的查询语句查询出来。

而 NoSQL 暂未提供类似 JOIN 的查询方式对多个数据集中的数据做查询。所以大部分 NoSQL 使用非规范化的数据存储方式存储数据。

5. **数据耦合性**

SQL 中不允许删除已经被使用的外部数据,例如审核人表中的”熊三”已经被分配给了借阅人熊大,那么在审核人表中将不允许删除熊三这条数据,以保证数据完整性。

而 NoSQL 中则没有这种强耦合的概念,可以随时删除任何数据。

6. **事务**

SQL 中如果多张表数据需要同批次被更新,即如果其中一张表更新失败的话其他表也不能更新成功。这种场景可以通过事务来控制,可以在所有命令完成后再统一提交事务。

而 NoSQL 中没有事务这个概念,每一个数据集的操作都是原子级的。

7. **语法**
  1. 什么是 MongoDB?
    1. 官方简介

MongoDB 是由 C++语言编写的,是一个基于分布式文件存储的开源数据库系统。

在高负载的情况下,添加更多的节点,可以保证服务器性能。

MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

  1. 主要特点

MongoDB 的提供了一个面向文档存储,操作起来比较简单和容易。

你可以在 MongoDB 记录中设置任何属性的索引 (如:FirstName=”Sameer”,Address=”8 Gandhi Road”)来实现更快的排序。

你可以通过本地或者网络创建数据镜像,这使得 MongoDB 有更强的扩展性。

如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。

Mongo 支持丰富的查询表达式。查询指令使用 JSON 形式的标记,可轻易查询文档中内嵌的对象及数组。

MongoDb 使用 update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。

Mongodb 中的 Map/reduce 主要是用来对数据进行批量处理和聚合操作。

Map 和 Reduce。Map 函数调用 emit(key,value)遍历集合中所有的记录,将 key 与 value 传给 Reduce 函数进行处理。

Map 函数和 Reduce 函数是使用 Javascript 编写的,并可以通过 db.runCommand 或 mapreduce 命令来执行 MapReduce 操作。

GridFS 是 MongoDB 中的一个内置功能,可以用于存放大量小文件。

MongoDB 允许在服务端执行脚本,可以用 Javascript 编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。

MongoDB 支持各种编程语言:RUBY,PYTHON,JAVA,C++,PHP,C#等多种语言。

MongoDB 安装简单。

  1. 概念解析

不管我们学习什么数据库都应该学习其中的基础概念,在 mongodb 中基本的概念是文档、集合、数据库,下面我们挨个介绍。

下表将帮助您更容易理解 Mongo 中的一些概念:

| SQL 术语/概念 | MongoDB 术语/概念 | 解释/说明 |
|:—-:|:—-:|:—-:|:—-:|:—-:|:—-:|
| database | database | 数据库 |
| table | collection | 数据库表/集合 |
| row | document | 数据记录行/文档 |
| column | field | 数据字段/域 |
| index | index | 索引 |
| table joins | | 表连接,MongoDB 不支持 |
| primary key | primary key | 主键,MongoDB 自动将_id 字段设置为主键 |

  1. MongoDB 数据类型

下表为 MongoDB 中常用的几种数据类型。

| 数据类型 | 描述 |
|:—-:|:—-:|:—-:|:—-:|
| String | 字符串。存储数据常用的数据类型。在 MongoDB 中,UTF-8 编码的字符串才是合法的。 |
| Integer | 整型数值。用于存储数值。根据你所采用的服务器,可分为 32 位或 64 位。 |
| Boolean | 布尔值。用于存储布尔值(真/假)。 |
| Double | 双精度浮点值。用于存储浮点值。 |
| Min/Max keys | 将一个值与 BSON(二进制的 JSON)元素的最低值和最高值相对比。 |
| Arrays | 用于将数组或列表或多个值存储为一个键。 |
| Timestamp | 时间戳。记录文档修改或添加的具体时间。 |
| Object | 用于内嵌文档。 |
| Null | 用于创建空值。 |
| Symbol | 符号。该数据类型基本上等同于字符串类型,但不同的是,它一般用于采用特殊符号类型的语言。 |
| Date | 日期时间。用 UNIX 时间格式来存储当前日期或时间。你可以指定自己的日期时间:创建 Date 对象,传入年月日信息。 |
| Object ID | 对象 ID。用于创建文档的 ID。 |
| Binary Data | 二进制数据。用于存储二进制数据。 |
| Code | 代码类型。用于在文档中存储 JavaScript 代码。 |
| Regular expression | 正则表达式类型。用于存储正则表达式。 |

  1. MongoDB 安装

  2. 数据库基础操作(语法)

    1. 数据库/集合(创建、删除)

      1
      2
      3
      4
      5
      use some1lee;//如果数据库不存在,则创建数据库,否则切换到指定数据库。
      show dbs;//查找所有数据库
      db.dropDatabase();//删除当前选中的数据库
      db.collection.insert();//插入文档,如果集合不存在,则创建集合。
      db.collection.drop();//删除指定的集合
    2. 文档(插入、更新、删除)

      1
      2
      3
      4
      5
      6
      7
      db.col.insert({title: 'MongoDB 教程',
      description: 'MongoDB 是一个 Nosql 数据库',
      by: 'MongoDB中文网',
      url: 'http://www.mongodb.org.cn',
      tags: ['mongodb', 'database', 'NoSQL'],
      likes: 100
      }) //插入文档

我们也可以将数据定义为一个变量,如下所示:

1
2
3
4
5
6
7
8
document=({title: 'MongoDB 教程',
description: 'MongoDB 是一个 Nosql 数据库',
by: 'Mongodb中文网',
url: 'http://www.mongodb.org.cn',
tags: ['mongodb', 'database', 'NoSQL'],
likes: 100
});//定义变量
db.col.insert(document);//执行插入

接着我们通过 update() 方法来更新标题(title):

1
2
3
4
5
6
7
8
db.col.update(
{'title':'MongoDB 教程'},#update的查询条件,类似sql内的where
{$set:{'title':'MongoDB'}},#update的对象,类似sql内的set
{
upsert: false,#可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。
multi: false,#可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。
writeConcern: {w:0}#可选,抛出异常的级别。
})

删除文档

1
2
3
4
5
6
7
db.collection.remove(     
{'title':'MongoDB'},# (可选)删除的文档的条件。{}标识删除所有
{
justOne: false,#可选)如果设为 true 或 1,则只删除一个文档。
writeConcern: {w:0} #(可选)抛出异常的级别。
}
)
  1. 查询
    1
    2
    3
    db.col.find();//查询所有文档
    db.col.find().pretty();//查询所有文档,并格式化返回结果
    db.col.findOne();//查询一个文档

MongoDB 与 RDBMS Where 语句比较
如果你熟悉常规的 SQL 数据,通过下表可以更好的理解 MongoDB 的条件语句查询:

操作 格式 范例 RDBMS中的类似语句
等于 {:} db.col.find({“by”:”菜鸟教程”}).pretty() where by = ‘菜鸟教程’
小于 {:{$lt:}} db.col.find({“likes”:{$lt:50}}).pretty() where likes < 50
小于或等于 {:{$lte:}} db.col.find({“likes”:{$lte:50}}).pretty() where likes <= 50
大于 {:{$gt:}} db.col.find({“likes”:{$gt:50}}).pretty() where likes > 50
大于或等于 {:{$gte:}} db.col.find({“likes”:{$gte:50}}).pretty() where likes >= 50
不等于 {:{$ne:}} db.col.find({“likes”:{$ne:50}}).pretty() where likes != 50

MongoDB AND 条件

MongoDB 的 find() 方法可以传入多个键(key),每个键(key)以逗号隔开,及常规 SQL 的 AND 条件。

语法格式如下:

1
db.col.find({key1:value1, key2:value2}).pretty();

MongoDB OR 条件
MongoDB OR 条件语句使用了关键字 $or,语法格式如下:

1
db.col.find({$or:[{key1:value1}, {key2:value2}]}).pretty();

AND 和 OR 联合使用

1
2
3
4
db.col.find(
{key:value},
{$or:[{key1:value1}, {key2:value2}]}
).pretty();

MongoDB Limit() 方法
如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。

1
db.col.find().limit(NUMBER);

MongoDB Skip() 方法
我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。

1
db.col.find().limit(NUMBER).skip(NUMBER);

MongoDB sort()方法
在MongoDB中使用使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。

1
db.col.find().sort({KEY:1});
  1. 高级查询
    1. 索引

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。

这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。

索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构

1
db.col.ensureIndex({KEY:1})

语法中 Key 值为你要创建的索引字段,1为指定按升序创建索引,如果你想按降序来创建索引指定为-1即可。
ensureIndex() 接收可选参数,可选参数列表如下:

Parameter Type Description
background Boolean 建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 “background” 可选参数。 “background” 默认值为false
unique Boolean 建立的索引是否唯一。指定为true创建唯一索引。默认值为false.
name string 索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。
dropDups Boolean 在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.
sparse Boolean 对文档中不存在的字段数据不启用索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.
expireAfterSeconds integer 指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。
v index version 索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。
weights document 索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。
default_language string 对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语
language_override string 对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language。
  1. 聚合

MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。有点类似sql语句中的 count(*)。

1
db.col.aggregate(AGGREGATE_OPERATION);

下表展示了一些聚合的表达式:
| 表达式 | 描述 | 实例 |
|:—-|:—-|:—-|
| $sum | 计算总和。 | db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$sum : “$likes”}}}]) |
| $avg | 计算平均值 | db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$avg : “$likes”}}}]) |
| $min | 获取集合中所有文档对应值得最小值。 | db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$min : “$likes”}}}]) |
| $max | 获取集合中所有文档对应值得最大值。 | db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$max : “$likes”}}}]) |
| $push | 在结果文档中插入值到一个数组中。 | db.mycol.aggregate([{$group : {_id : “$by_user”, url : {$push: “$url”}}}]) |
| $addToSet | 在结果文档中插入值到一个数组中,但不创建副本。 | db.mycol.aggregate([{$group : {_id : “$by_user”, url : {$addToSet : “$url”}}}]) |
| $first | 根据资源文档的排序获取第一个文档数据。 | db.mycol.aggregate([{$group : {_id : “$by_user”, first_url : {$first : “$url”}}}]) |
| $last | 根据资源文档的排序获取最后一个文档数据 | db.mycol.aggregate([{$group : {_id : “$by_user”, last_url : {$last : “$url”}}}]) |

  1. 拓展

    1. 副本集(复制)
    2. 分片
    3. 备份与恢复
    4. 监控
  2. SpringBoot中使用MongoDB

    1. Maven引用地址

      1
      2
      3
      4
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-mongodb</artifactId>
      </dependency>
    2. 连接数据库

连接数据库,你需要指定数据库名称,如果指定的数据库不存在,mongo会自动创建数据库。

SpringBoot配置如下:

1
2
3
4
5
6
#MongoDB配置
spring.data.mongodb.database=some1lee
spring.data.mongodb.host=192.168.100.108
spring.data.mongodb.port=27017
#spring.data.mongodb.username=
#spring.data.mongodb.password=
  1. 引用SpringData中的MongoTemplate

    1
    2
    @Resource
    private MongoTemplate mongoTemplate;
  2. 创建实体

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Document(collection = "user") //用来表明关联的mongo中的那个collection(类似于表名)
    public class UserPO implements Serializable{
    @Indexed //为某个字段建立索引
    @Field("user_id")
    private Long userId;

    @Field("username") //声明属性对应的数据库中的哪个字段
    private String username;

    @Field("password")
    private String password;

    ...
    }
    也可以不使用实体,保存的时候指定集合
  3. 获取文档

    1
    2
    3
    Query query = new Query();
    query.addCriteria(Criteria.where("user_id").is(1L));
    List<UserPO> userPOS = mongoTemplate.find(query, UserPO.class);
  4. 插入文档

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //插入一条
    UserPO userPO = new UserPO();
    userPO.setUserId(1L);
    userPO.setUsername("username");
    userPO.setPassword("password");
    mongoTemplate.insert(userPO);

    //插入多条
    LinkedList<UserPO> pos = Lists.newLinkedList();
    UserPO userPO = new UserPO();
    userPO.setUserId(1L);
    userPO.setUsername("username");
    userPO.setPassword("password");
    pos.add(userPO); //集合中可以放多个
    mongoTemplate.insertAll(pos);
    //若没有当前集合,会自动新增
  5. 更新文档

    1
    2
    3
    4
    5
    Query query = new Query();
    query.addCriteria(Criteria.where("user_id").is(1L));
    Update update = new Update();
    update.set("username","the new username");
    mongoTemplate.findAndModify(query,update,UserPO.class);
  6. 删除文档

    1
    2
    3
    Query query = new Query();
    query.addCriteria(Criteria.where("user_id").is(1L));
    mongoTemplate.findAndRemove(query,UserPO.class);
  1. 参考资料

    https://www.mongodb.org.cn/ MongoDB中文官网
    https://www.mongodb.org.cn/manual/Collection/ MongoDB Collection Method(集合方法)
    https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/MongoTemplate.html MongoTemplate API