前言
什么是mongodb?MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
mongodb的基本用法
链接mongodb数据库
在windows下,MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包,你可以从MongoDB官网下载安装,MongoDB 预编译二进制包下载地址:https://www.mongodb.com/download-center#community,安装后自行搜索配置环境变量,其他操作系统下也自行搜索安装方法,以下操作默认已经配置好环境变量。
- 打开命令行工具,执行mongod --dbpath c:\data\db,这里的路径跟的是你自己设置的存储mongodb数据的路径。
- 另开一个命令行工具,执行mongo,即进入mongodb命令窗口。
数据库的基本操作常用命令
- show dbs --显示所有数据库
- use test --使用test数据库
- db.dropDatabase() --删除当前所use的数据库
- show tables --显示当前数据库下所有集合
- db.foo.insert({}) --向foo集合下插入数据
- db.foo.find() --查询foo集合下所有数据
- db.foo.update({}) --更新数据
- db.foo.remove({}) --删除数据
find查询
db.collection.find(query, projection)
-
- query :可选,使用查询操作符指定查询条件
-
- projection:可选,使用投影操作符指定返回的键。查询时返回文档中所有键值, 只需省略该参数即可(默认省略)。
pretty() 方法以格式化的方式来显示所有文档。
db.collection.find(query, projection).pretty()
> db.col.find().pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程"
}
复制代码
MongoDB同样还支持条件语句查询,如:
-
- 等于 : { key : value }
-
- 小于 : { key : { $lt : value } }
-
- 小于或等于 : { key : { $lte : value } }
-
- 大于 :{ key : { $gt : value } }
-
- 大于或等于 : { key : { $gte : value } }
-
- 不等于 : { key : { $ne : value } }
下面这个小例子表示查询sort不等于50的数据,代码如下所示:
> db.col.find({"sort":{$ne:50}}).pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程",
"sort" : 20
}
复制代码
find的条件中支持and和or查询,也就是“并”与“或”的关系, find() 方法中传入多个键(key),每个键(key)以逗号隔开,表示and,or则是用数组包裹,下面直接上代码。
> db.col.find({"likes": {$gt:50}, $or: [{"by": "MongoDB"},{"title": "MongoDB 教程"}]}).pretty()
{
"_id" : ObjectId("56063f17ade2f21f36b03133"),
"title" : "MongoDB 教程",
"description" : "MongoDB 是一个 Nosql 数据库",
"by" : "MongoDB",
"likes" : 100
}
复制代码
下面我们继续讨论MongoDB中条件操作符 $type,如果想获取 "col" 集合中 title 为 String 的数据,你可以使用以下命令
db.col.find({"title" : {$type : 2}})
> db.col.find({"title" : {$type : 2}})
{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "likes" : 200 }
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "likes" : 150 }
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "likes" : 100 }
复制代码
如果你需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数。
> db.col.find({},{"title":1,_id:0}).limit(2)
{ "title" : "PHP 教程" }
{ "title" : "Java 教程" }
复制代码
我们除了可以使用limit()方法来读取指定数量的数据外,还可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数。
>db.col.find({},{"title":1,_id:0}).limit(1).skip(1)
{ "title" : "Java 教程" }
复制代码
在MongoDB中使用使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列。
>db.col.find({},{"title":1,_id:0}).sort({"likes":-1})
{ "title" : "PHP 教程" }
{ "title" : "Java 教程" }
{ "title" : "MongoDB 教程" }
复制代码
注意:当查询时同时使用sort,skip,limit,无论位置先后,最先执行顺序 sort再skip再limit。
MongoDB 索引
索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。
索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。
索引优点:加快索引相关的查询;
索引缺点:增加磁盘空间消耗,降低写入性能。
索引的基本操作
getIndexes() =>查看索引
ensureIndex() =>创建索引
dropIndex("name_1") =>删除索引
_id索引
_id索引是绝大多数集合默认建立的索引。对于每个插入的数据,mongodb都会自动生成一条唯一的_id字段。如
>db.col.insert({"title":1})
{ "title" : "1","_id" : ObjectId("5a2de799328eaf1eb0af7849") }
复制代码
单键索引
单键索引是最普通的索引,与_id索引不同,单键索引不会自动创建
>db.col.ensureIndex({"title":1})
复制代码
多键索引
多键索引与单键索引创建形式相同,区别在于字段的值。单键索引,值为一个单一的值,例如字符串、数字或者日期;多键索引的值具有多个记录,例如数组。
>db.col.ensureIndex({"title":1})
复制代码
复合索引
对多个键创建的索引,比如,需要对Mail的user与folderId创建降序索引:
>db.col.ensureIndex({"title":1."name": -1})
复制代码
过期索引
过期索引是在一段时间后会过期的索引,在索引过期后,相应的数据会被删除,这适合存储一些在一段时间后会失效的数据比如用户的登录信息、存储的日志等。
>db.col.ensureIndex({"title":1}, { expireAfterSeconds:60 })
复制代码
需要注意的是,过期索引会存在误差,大概60s刷新一次;存储在过期索引字段的值必须是指定的时间类型,不能是时间戳;如果指定了Date数组,则按照最小的时间进行删除; 过期索引不能是复合索引。
全文索引
全文索引是对字符串与字符串数组创建全文可搜索的索引,使用情况如下所示
{author: '', title: '', article: ''}
全文索引最常用的建立方法有三种,其中value必须是"text",对单个字段建立全文索引可选第一种,对多个字段可选第二种,对所有字段可选最后一种,其中要注意的是,在mongodb中只能存在一种全文索引。
db.col.ensureIndex({"key":"text"})
db.col.ensureIndex({"key1":"text","key2":"text"})
db.col.ensureIndex({"$**":"text"})
> db.col.ensureIndex({name:"text"})
> db.col.ensureIndex({name:"text",title:"text"})
> db.col.ensureIndex({"$**":"text"})
复制代码
那么如何使用全文索引查询呢?
db.col.find({$text:{$search:"aaa"}}) =>查询包含aaa
db.col.find({$text:{$search:"aaa bb"}}) =>查询包含aaa或者包含bb
db.col.find({$text:{$search:"aaa bb -cc"}}) =>查询包含aaa或者bb,但是不包含cc
db.col.find({$text:{$search:""aaa""bb""}}) =>查询包含aaa和bb
> db.test.find({$text:{$search:"aa"}})
{ "_id" : ObjectId("5a321f7f071884f56c1baba4"), "name" : "aa bb cc" }
{ "_id" : ObjectId("5a321f83071884f56c1baba5"), "name" : "aa bb" }
复制代码
全文索引的另一个黑科技,那就是全文索引相似度了,全文索引相似度指的是$meta操作符:{score:{$meta:"textScore"}}写在查询条件后面可以返回结果的相似度,与sort一起使用,可以达到很好的效果。
> db.test.find({$text:{$search:"aa"}},{score:{$meta:"textScore"}})
{ "_id" : ObjectId("5a321f7f071884f56c1baba4"), "name" : "aa bb cc", "score" : 0.6666666666666666 }
{ "_id" : ObjectId("5a321f83071884f56c1baba5"), "name" : "aa bb", "score" : 0.75 }
复制代码
全文索引虽然非常强大,但是同样存在着一些限制,例如:每次查询,只能指定一个$text查询;$text查询不能出现在$nor查询中;查询中如果包含了$text,hint不再起作用。
地理位置索引
地理位置索引指的是将一些点的位置存储在mongodb中,创建索引后,可以按照位置来查找其他的点。
地理位置索引分为两类,一种是2d索引表示平面地理位置索引,即用于存储和查找平面上的点,另一种是2dsphere索引表示球面地理位置索引,用于存储和查找球面上的点。
查找方式也可大致分为两种:
1.查找距离某个点一定距离内的点;
2.查找包含在某区域内的点
下面我们先来说说2d索引,创建方式如下所示:
db.col.ensureIndex({ w : "2d"})
位置表示方式:经纬度[经度,纬度]
取值范围:经度[-180,180]纬度[-90,90]
查询方式:
(1)$near查询:查询距离某个点最近的点,默认返回一百个离我最近的点,可使用$maxDistance来限制最大范围
> db.col.find({w:{$near:[1,1],$maxDistance:20}})
{ "_id" : ObjectId("5a32297d071884f56c1baba8"), "w" : [ 1, 1 ] }
{ "_id" : ObjectId("5a322981071884f56c1baba9"), "w" : [ 1, 11 ] }
{ "_id" : ObjectId("5a322986071884f56c1babaa"), "w" : [ 1, 21 ] }
复制代码
(2)$geoWithin查询:查询某个形状内的点,其中形状的表示有以下几种:
$box:矩形,使用{ $box : [ [ x1, y1 ] , [ x2, y2 ] ] }
> db.col.find({ w:{$geoWithin:{$box:[ [1,1],[2,3] ]}} })
{ "_id" : ObjectId("5a32297d071884f56c1baba8"), "w" : [ 1, 1 ] }
复制代码
$center:圆形,使用{ $center: [[ x1, y1 ], r] }表示
> db.col.find({ w:{$geoWithin:{$center:[ [1,1],10 ]}} })
{ "_id" : ObjectId("5a32297d071884f56c1baba8"), "w" : [ 1, 1 ] }
{ "_id" : ObjectId("5a322981071884f56c1baba9"), "w" : [ 1, 11 ] }
复制代码
$polygon: 多边形,使用{ $polygon: [ [ x1, y1 ], [ x2, y2 ], [ x3, y3 ] ] }表示
> db.col.find({ w:{$geoWithin:{$polygon:[ [1,1],[20,10],[6,20] ]}} })
{ "_id" : ObjectId("5a32297d071884f56c1baba8"), "w" : [ 1, 1 ] }
复制代码
(3)geoNear查询:geoNear使用runCommand命令进行使用,常用使用如下
db.runCommand( { geoNear: col, near: [x, y], minDistance: (对2d索引无效), maxDistancen: , num: 返回条数 } )
> db.runCommand({ geoNear: "col", near:[1,2], maxDistance: 10, num: 1 })
{
"results" : [
{
"dis" : 1,
"obj" : {
"_id" : ObjectId("5a32297d071884f56c1baba8"),
"w" : [
1,
1
]
}
}
],
"stats" : {
"nscanned" : 4,
"objectsLoaded" : 1,
"avgDistance" : 1,
"maxDistance" : 1,
"time" : 0
},
"ok" : 1
}
复制代码
接着我们来聊聊2dsphere索引,2dsphere表示球面地理位置索引,创建方式如下所示:
db.col.ensureIndex({ w: 2dsphere })
位置表示方式:GeoJSON:描述一个点,一条直线,多边形等形状。格式如下所示:
{ type: '', coordinates: [ coordinates ] }
查询方式与2d索引查询方式类似,并且支持$minDistance,这一块的比较复杂,有兴趣的小伙伴可自行搜索。
索引的常用属性
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 之间,表示该索引相对于其他索引字段的得分权重。 |
索引构建情况分析
索引的好处:加快索引相关的查询。
索引的弊端:增加磁盘空间消耗,降低写入性能。 可通过以下四种途径来评判当前索引构建情况:
(1)mongostat工具
此工具是mongodb自带的一个检测工具,提供了关于mongod和mongos的当前运行状态概览,启动命令格式为:
mongostat -h 127.0.0.1:27017 -u root -p 123456
如果没有设置用户名和密码可省略-u及以后代码
F:\mongodb\bin>mongostat -h 127.0.0.1:27017
insert query update delete getmore command dirty used flushes vsize res qrw arw net_in net_out conn time
*0 *0 *0 *0 0 1|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 157b 45.1k 2 Dec 14 16:14:29.989
*0 *0 *0 *0 0 1|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 157b 45.2k 2 Dec 14 16:14:30.990
*0 *0 *0 *0 0 2|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 158b 45.5k 2 Dec 14 16:14:31.986
*0 *0 *0 *0 0 1|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 157b 45.2k 2 Dec 14 16:14:32.987
*0 *0 *0 *0 0 1|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 157b 45.2k 2 Dec 14 16:14:33.989
*0 *0 *0 *0 0 2|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 158b 45.3k 2 Dec 14 16:14:34.988
*0 *0 *0 *0 0 1|0 0.0% 0.0% 0 269M 14.0M 0|0 0|0 157b 45.3k 2 Dec 14 16:14:35.988
复制代码
字段解释:
名字段 | 说明 |
---|---|
insert/s | 官方解释是每秒插入数据库的对象数量,如果是slave,则数值前有*,则表示复制集操作 |
query/s | 每秒的查询操作次数 |
update/s | 每秒的更新操作次数 |
delete/s | 每秒的删除操作次数 |
getmore/s | 每秒查询cursor(游标)时的getmore操作数 |
command | 每秒执行的命令数,在主从系统中会显示两个值(例如 3|0),分表代表 本地|复制 命令。注: 一秒内执行的命令数比如批量插入,只认为是一条命令(所以意义应该不大) |
dirty | 仅仅针对WiredTiger引擎,官网解释是脏数据字节的缓存百分比 |
used | 仅仅针对WiredTiger引擎,官网解释是正在使用中的缓存百分比 |
flushes | For WiredTiger引擎:指checkpoint的触发次数在一个轮询间隔期间;For MMAPv1 引擎:每秒执行fsync将数据写入硬盘的次数。注:一般都是0,间断性会是1, 通过计算两个1之间的间隔时间,可以大致了解多长时间flush一次。flush开销是很大的,如果频繁的flush,可能就要找找原因了 |
vsize | 虚拟内存使用量,单位MB (这是 在mongostat 最后一次调用的总数据) |
res | 物理内存使用量,单位MB (这是 在mongostat 最后一次调用的总数据)。注:这个和你用top看到的一样, vsize一般不会有大的变动, res会慢慢的上升,如果res经常突然下降,去查查是否有别的程序狂吃内存。 |
qr | 客户端等待从MongoDB实例读数据的队列长度 |
qw | 客户端等待从MongoDB实例写入数据的队列长度。 |
ar | 执行读操作的活跃客户端数量 |
aw | 执行写操作的活客户端数量。注:如果这两个数值很大,那么就是DB被堵住了,DB的处理速度不及请求速度。看看是否有开销很大的慢查询。如果查询一切正常,确实是负载很大,就需要加机器了 |
netIn | MongoDB实例的网络进流量 |
netOut | MongoDB实例的网络出流量。注:此两项字段表名网络带宽压力,一般情况下,不会成为瓶颈 |
conn | 打开连接的总数,是qr,qw,ar,aw的总和 |
注:MongoDB为每一个连接创建一个线程,线程的创建与释放也会有开销,所以尽量要适当配置连接数的启动参数,maxIncomingConnections,阿里工程师建议在5000以下,基本满足多数场景。
profile集合
> db.system.profile.find()
复制代码
这种方式是将所有日志新建了一个数据库存储进去的,对性能有较大影响,不推荐这种方式。
日志
我们也可以在mongodb的日志中查看操作的详细信息,并且可以通过conf/mongod.conf中的verbose参数进行配置,verbose的有v-vvvvv五个等级,等级越高,日志越详细,可以通过log/mongod.log文件查看mongodb的日志信息。
explain
explain()能够提供大量与查询相关的信息。对于速度比较慢的查询来说,这是最重要的诊断工具之一。通过查看一个查询的explain()输出信息,可以知道查询使用了哪个索引,以及是如何使用的。
最常见的explain()的输出有两种类型:使用索引的查询和没有使用索引的查询。
- 没有使用索引时
{
"cursor" : "BasicCursor",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1001,
"nscanned" : 1001,
"nscannedObjectsAllPlans" : 1001,
"nscannedAllPlans" : 1001,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 7,
"nChunkSkips" : 0,
"millis" : 1,
"server" : "user:27017",
"filterSet" : false
}
复制代码
- 使用索引时
{
"cursor" : "BtreeCursor username_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"username" : [
[
"user1000",
"user1000"
]
]
},
"server" : "user:27017",
"filterSet" : false
}
复制代码
下面我们以有索引的结果为例,来依次看一下这些字段代表的意思。
-
"cursor" : "BtreeCursor username_1" BtreeCursor表示本次查询使用了索引,具体来说,是使用了“username”上的索引{“username”:1}。如果查询要对结果进行逆序遍历,或者使用了多键索引,就可以在这个字段中看到“reverse”和“multi”这样的值。
-
"isMultiKey" : false 用于说明本次是否使用了多键索引。
-
"n" : 1 本次查询返回的文档数量
-
"nscannedObjects" : 1 这是MongoDB按照索引指针去磁盘上查找实际文档的次数。如果查询包含的查询条件不是索引的一部分,或者说要求返回不在索引内的字段,MongoDB就必须依次查找每个索引条目指向的文档。
-
"nscanned" : 1 如果有使用索引,那么这个数字就是查找过的索引条目数量,如果本次查询是一次全表扫描,那么这个数字就代表检查过的文档数目。
-
"scanAndOrder" : false MongoDB是否在内存中对结果集进行了排序
-
"indexOnly" : false MongoDB是否只使用索引就能完成此次查询。在本例中,MongoDB只使用索引就找到了全部的匹配文档,从“nscanned”和“n”相等就可以看出来。然而,本次查询要就返回匹配文档中的所有字段,而索引只包含“username”这个字段,如果就本次查询修改为{"_id":0, "username":1},那么本次查询就可以被索引覆盖了,"indexOnly"的值就会是true。
-
"nYields" : 0 为了让写入请求能够顺利执行,本次查询暂停暂停的次数。如果有写入请求需求处理,查询会周期性的释放他们的锁,以便写入能够顺利执行。然而,在本次查询中,没有写入请求,因此查询没有暂停过。
-
"millis" : 0 数据库执行本次查询所耗费的毫秒数。这个数字越小,说明效率越高。
-
"indexBounds" : {...} 这个字段描述了索引的使用情况,给出了索引的遍历范围。由于此次查询是精确匹配,所以所以只要查“user1000”这个值就可以了。