MongoDB一款非关系型的数据库,不支持外键,不支持事务,不支持数据类型约定。传统数据库由于受到各种关系的约束,各种数据形式的束缚,难以处理海量数据以及超高并发的业务场景。为了解决上述问题,所以需要抛弃关系的约束,以求在更高层次上突破瓶颈的数据库系统。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富、最像关系数据库的。由于关系型数据库存储对数据之间存在高度的关联,在数据量达到上万亿比特时,关系型数据库所特有的约束和关联就会成为性能瓶颈。非关系型数据库采用了另一种思维方式,即不考虑数据之间千丝万缕的联系,存储也不需要固定的模式,这样无需多余的操作就能成倍地扩展数据量。
MongoDB 支持的数据结构非常松散,是类似 json 的 bson 格式,因此可以存储比较复杂的数据类型。
面向集合的存储
在 MongoDB 中,一个数据库包含多个集合,类似于 MySQL 中一个数据库包含多个表;一个集合包含多个文档,类似于 MySQL 中一个表包含多条数据。
可以把集合记为表,文档记为一条记录。这样命名是有原因的,因为 MongoDB 没有行列统一的表格式排列,而是采用一个大仓库的形式将所有数据包纳其中。文档也一样,它是一段自由独立的数据,受外部限制少,所以区别于关系型数据库的记录。
启动mongodb服务
根据已配置的配置文件进行启动
mongod --config /usr/local/mongodb/mongodb.conf
进入mongo命令行
mongo
基本概念
数据库
show dbs #查看所有数据库的列表,MongoDB 不会显示空的数据库或显示 dbName(empty)。
db #查看当前连接的数据库
use #连接(创建)指定的数据库
注意:数据库名可以是任何字符,但是不能有空格、点号和 $ 字符。
use mydb #创建数据库
db.dropDatabase() #销毁数据库
文档
文档是 MongoDB 的核心,类似于关系数据库中的每一行数据。多个键及其关联的值放在一起就是文档。在 Mongodb 中使用一种类 json 的 bson 存储数据,bson 数据可以理解为在 json 的基础上添加了一些 json 中没有的数据类型。
文档的逻辑联系
假设有两个文档:# user文档 { "name": "Tom Hanks", "contact": "987654321", "dob": "01-01-1991" } # address文档 { "building": "22 A, Indiana Apt", "pincode": 123456, "city": "chengdu", "state": "sichuan" }
- 关系 1:嵌入式关系
把 address 文档嵌入到 user 文档中# 这就是嵌入式的关系 { "name": "Tom Hanks", "contact": "987654321", "dob":"01-01-1991", "address": [{ "building": "22 A, Indiana Apt", "pincode": 123456, "city": "chengdu", "state": "sichuan" }, { "building": "170 A, Acropolis Apt", "pincode": 456789, "city": "beijing", "state": "beijing" }] }
- 关系 2:引用式关系
将两个文档分开,通过引用文档的_id 字段来建立关系# 这就是引用式关系 { "name": "Tom Benzamin", "contact": "987654321", "dob": "01-01-1991", "address_ids": [ ObjectId("52ffc4a5d85242602e000000") #对应address文档的id字段 ] }
在实际应用的时候,嵌入式关系比较适合一对一的关系,引用式关系比较适合一对多或者多对多的情况。
集合
集合就是一组文档的组合,就相当于是关系数据库中的表,在 MongoDB 中可以存储不同的文档结构的文档。
例如:
{"company":"Chenshi keji"} {"people":"man","name":"peter"}
上面两个文档就可以存储在同一个集合中,在关系型数据库中是很难实现上述数据结构的,要么需要定义大量的字段,对于一些字段名不确定的属性,关系型数据库会更加力不从心。
MongoDB 的做法也不是完美的。比如在存储用户信息的时候,用户名和密码分别用 username 和 password 字段表示。 关系型数据库只需要把字段名作为表结构的一部分保存起来就可以了,而 MongoDB 需要将这两个字段名存储多次,每一条记录都会存储一次字段名,在一些原本就比较碎片化的字段上,用于存储字段名所耗费的空间甚至会超过存储数值的空间,比较好的解决办法是采用尽可能短的字段名,不过这又涉及到了可读性的问题,需要大家在二者之间进行权衡。
show collections #查看当前数据库的所有集合
db.createCollection("") #创建无参数集合
db.createCollection("", { capped : 1, autoIndexId : 1, size : 6142800, max : 10000 } ) #创建带参数集合
db.COLLECTION_NAME.drop() #删除集合
参数描述:
capped:类型为 Boolean,如果为 true 则创建一个固定大小的集合,当其条目达到最大时可以自动覆盖以前的条目。在设置其为true 时也要指定参数大小;
autoIndexId:类型为 Boolean,默认为 false,如果设置为 true,则会在 _id字段上自动创建索引;
size:如果 capped 为 true 则需要指定,指定参数的最大值,单位为 byte;
max:指定最大的文档数。
元数据
数据库的信息存储在集合中,他们统一使用系统的命名空间:DBNAME.system.*
。
DBNAME 可用 db 或数据库名替代:
- DBNAME.system.namespaces :列出所有名字空间
- DBNAME.system.indexs :列出所有索引
- DBNAME.system.profile :列出数据库概要信息
- DBNAME.system.users :列出访问数据库的用户
- DBNAME.system.sources :列出服务器信息
CRUD
- 插入数据
#插入数据时,如果 users 集合没有创建会自动创建。
#使用 insert()
> use mydb
switched to db mydb
> db.users.insert([
{
name : "jam",
email : "jam@qq.com"
},
{
name : "tom",
email : "tom@qq.com"
}
])
#使用 save()
> use mydb
switched to db mydb
> db.users.save([
{
name : "jam",
email : "jam@qq.com"
},
{
name : "tom",
email : "tom@qq.com"
}
])
insert 和 save 的区别:为了方便记忆,可以先从字面上进行理解,insert 是插入,侧重于新增一个记录的含义;save 是保存,可以保存一个新的记录,也可以保存对一个记录的修改。因此,insert 不能插入一条已经存在的记录,如果已经有了一条记录(以主键为准),insert 操作会报错,而使用 save 指令则会更新原记录。
- 查询语句
db.COLLECTION_NAME.find() #查询数据,不加任何参数默认返回集合中的所有文档,实际应用中不常见,因为这样会导致大量的数据传输,造成服务器响应迟缓甚至失去响应。
db.COLLECTION_NAME.find({ key1: value1, key2: value2 }) #MongoDB 不需要类似于关系型数据库的 AND 运算符,当 find() 中传入多个键值对时,MongoDB 就会将其作为 AND 查询处理。
db.post.find(
{
$or: [
{key1: value1}, {key2:value2}
]
}
) #MongoDB 中,OR 查询语句以 $or 作为关键词
#MongoDB 的模糊查询可以用正则匹配的方式实现
{"name":/^start/} #如: 以 'start' 开头的匹配式
{"name":/tail$/} #如: 以 'tail' 结尾的匹配式
db.COLLECTION_NAME.find().limit() #读取指定数量的数据记录
db.COLLECTION_NAME.find().skip() #读取时跳过指定数量的数据记录
db.COLLECTION_NAME.find().sort({KEY:1|-1}) # 排序,其中升序用 1 表示,降序用 -1 表示
如下例:
db.mydb.find({
"likes": {$lte:20,$gt:10},
"name": {$type:2},
$or: [
{"by": "abc"},
{"title": "MongoDB Overview"}
]
}).limit(1).skip(1).sort({"time":1}) # 其中"name":{$type:2}等价于"name":{$type:'string'}
条件操作符:
- gt:大于 greater than
- lt:小于 less than
- gte:大于或等于 greater than equal
- lte:小于或等于 less than equal
- ne:不等于 not equal
- $type:[key]
1: 双精度型(Double)
2: 字符串(String)
3: 对象(Object)
4: 数组(Array)
5: 二进制数据(Binary data)
7: 对象 ID(Object id)
8: 布尔类型(Boolean)
9: 日期(Date)
10: 空(Null)
11:正则表达式(Regular Expression)
13: JS 代码(Javascript)
14: 符号(Symbol)
15:、有作用域的 JS 代码(JavaScript with scope)
16: 32 位整型数(32-bit integer)
17:时间戳(Timestamp)
18: 64 位整型数(64-bit integer)
-1: 最小值(Min key)
127: 最大值(Max key)
- 更新文档
db.COLLECTION_NAME.update(SELECTION_CRITERIA,UPDATED_DATA<,multi:true>)
如下例:
db.mydb.update({"e-mail":"test@qq.com"},{$set:{"e-mail":"group@qq.com"}},{multi:true}) #将 user_id=2 的文档的 e-mail 改为 group@qq.com
- 第一个大括号内容标示查找条件,第二个大括号内容则表示更新后的数据
- 默认的 update 函数只对一个文档更新,如果只对一个文档更新则不需要加入 multi:true,如果想作用所有文档,则需要加入 multi:true
- 删除文档
db.COLLECTION_NAME.remove(DELECTION_CRITERIA) #第一个参数相当于条件,删除指定条件的文档
- 替换文档
db.COLLECTION_NAME.save({_id:ObjectId(),NEW_DATA}) #_id即指定要替换掉的文档
如下例:
db.mydb.save({"_id":ObjectId("53ea174ccb4c62646d9544f4"),"name":"Bob","position":"techer"})
索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB 在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可能要花费几十秒甚至几分钟,这无疑对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库集合中一个文档或多个文档的值进行排序的一种结构。
由于索引存在于 RAM 中,因而从索引中获取数据要比扫描文档更快。
db.COLLECTION_NAME.ensureIndex({KEY:1|-1}) # 1 代表升序,-1 代表降序
可选参数:
参数 类型 描述 background Boolean 建立索引要不要阻塞其他数据库操作,默认为 false unique Boolean 建立的索引是否唯一,默认 false name string 索引的名称,若未指定,系统自动生成 dropDups Boolean 建立唯一索引时,是否删除重复记录,默认 flase sparse Boolean 对文档不存在的字段数据不启用索引,默认 false expireAfterSeconds integer 设置集合的生存时间,单位为秒 v index version 索引的版本号 weights document 索引权重值,范围为 1 到 99999 default-language string 默认为英语 language_override string 默认值为 language
如下例:
db.mydb.ensureIndex({gender:1,user_name:1},{background:1})
对于上述查询,MongoDB 不会在数据库文件中查找,而会从索引中提取数据。因为索引中不包含 _id 字段,所以 _id 在查询中会默认返回,可以在查询结果中将其排除。
- 索引数组字段
在数组中创建索引,需要对数组中的每个字段依次建立索引。
如下例:
# 插入数据
db.users.insert({
"address":
{
"city": "chengdu",
"province": "sichuan",
"pincode": "123"
},
"tags":
[
"music",
"cricket",
"blogs"
],
"name": "clound"
})
# 创建索引,在我们为数组 tags 创建索引时,会为 music、cricket、blogs 三个值建立单独的索引。
db.users.ensureIndex({"tags":1})
#创建索引后,我们可以这样检索集合的 tags 字段
db.users.find({tags:"cricket"})
#为了验证我们使用了索引,可以使用 explain 命令
db.users.find({tags:"cricket"}).explain()
- 索引子文档字段
由于检索文档的字段是子文档的字段,所以我们需要对子文档建立索引
如下例:
# 插入数据
db.users.insert({
"address":
{
"city": "chengdu",
"province": "sichuan",
"pincode": "123"
},
"tags":
[
"music",
"cricket",
"blogs"
],
"name": "clound"
})
# 创建索引,为子文档的三个字段创建索引。假设我们需要通过 city、province、pincode 字段来检索文档
db.users.ensureIndex({"address.city":1,"address.province":1,"address.pincode":1})
#一旦创建索引,我们可以使用子文档的字段来检索数据
db.users.find({"address.city":"chengdu"})
#记住查询表达式必须遵循指定的索引的顺序。所以上面创建的索引将支持以下查询
db.users.find({"address.city":"chengdu","address.province":"sichuan"})
#同样支持以下查询
db.users.find({"address.city":"chengdu","address.province":"sichuan","address.pincode":"123"})
#为了验证我们使用了索引,可以使用 explain 命令
db.users.find({"address.city":"chengdu","address.province":"sichuan","address.pincode":"123"}).explain()
聚合
db.COLLECTION_NAME.aggregate({})
可选参数:
- $match:查询,用于过滤数据,跟 find 一样;
- $limit:用来限制 MongoDB 聚合管道返回的文档数;
- $skip:在聚合管道中跳过指定数量的文档;
- $sort:排序;
- $group:按照给定表达式将集合中的文档分组,可用于统计结果。
- $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档;
- $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值;
- $geoNear:输出接近某一地理位置的有序文档。
聚合表达式名称 | 描述 |
---|---|
$sum | 计算总和 |
$avg | 计算平均值 |
min和max | 计算最小值和最大值 |
$push | 在结果文档中插入值到一个数组 |
$addToSet | 在结果文档中插入值到一个数组,但不创建副本 |
$first | 根据资源文档的排序获取第一个文档数据 |
$last | 根据资源文档的排序获取最后一个文档数据 |
- 管道
MongoDB 的聚合管道将 MongoDB 文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。
表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。
如下例:
> db.mydb.aggregate([{$match:{user_id:{$gt:0,$lte:2}}},{$group:{_id:"user",count:{$sum:1}}}])
原子操作
所谓原子操作,就是要么执行成功,要么执行失败,执行成功完成既定任务,执行失败还原执行前的状态。
常用原子操作命令:
命令 | 作用 | 例子 |
---|---|---|
$set | 用来指定一个键并更新键值,若键不存在则创建 | { $set : { field : value } } |
$unset | 用来删除一个键 | { $unset : { field : 1} } |
$inc | 可以对文档的某个值为数字型(只能为满足要求的数字)的键进行增减的操作 | { $inc : { field : value } } |
$push | 把 value 追加到 field 里面去,field 一定要是数组类型才行,如果 field 不存在,会新增一个数组类型加进去 | { $push : { field : value } } |
$pushAll | 同 $push ,只是一次可以追加多个值到一个数组字段内 | { $pushAll : { field : value_array } } |
$pull | 从数组 field 内删除一个等于 value 值 | { $pull : { field : _value } } |
$addToSet | 增加一个值到数组内,而且只有当这个值不在数组内才增加 | |
$pop | 删除数组的第一个或最后一个元素 | { $pop : { field : 1 } } |
$rename | 修改字段名称 | { $rename : { old_field_name : new_field_name } } |
$bit | 位操作,integer 类型 | {$bit : { field : {and : 5}}} |
查询分析
explain()
操作提供了查询信息,使用索引及查询统计等。有利于我们对索引的优化。
hint()
可以用来强迫 MongoDB 使用指定的索引。通过这种方法在某些情形下会提升性能。
如下例:
db.users.find({tags:"cricket"}).hint({tags:1})