mongodb 索引、聚合操作

MongoDB索引

MongoDB的索引几乎与传统的关系型数据库索引一模一样。

创建索引的方法:

> db.trans.ensureIndex({card1: 1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

对于同一个集合,同样的索引只需要创建一次,反复创建是徒劳的。
对某个键创建的索引会加速对该键的查询,然而,对于其他查询是没有帮助的,即便是查询中包含了被索引的键。例如: 以上索引对以下查询不会有任何优化

> db.trans.find({card1: "489592", "card2" : "8055"})

一定要创建查询中用到的所有键的索引。

> db.trans.ensureIndex({card1: 1, card2: 1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 2,
    "numIndexesAfter" : 3,
    "ok" : 1
}

一组值为1或-1 的键表示索引创建的方向。
如果索引包含N个键,则对于前几个键的查询都会有帮助。比如有个索引{“a”: 1, “b”: 1, “c”: 1, …,”z”:1},实际上是有了{“a”: 1}、{“a”: 1, “b”:1}、{“a”: 1, “b”: 1, “c”: 1}等的索引。但是使用{“b”: 1}、{“a”:1 , “c”: 1}等索引的查询不会被优化,只有使用索引前部的查询才能使用该索引。

MongoDB的查询优化器会重排查询项的顺序,以便引用索引: 比如查询{“x”: “foo”, “y”: “bar”}的时候,已经有了{“y”: 1, “x”: 1}的索引,MongodB会自己找到并利用它。

创建索引的缺点就是每次插入、更新和删除都会产生额外的开销。这是因为数据库不但需要执行这些操作,还要将这些操作在集合的索引中标记。每个集合默认的最大索引个数为64个。

一定不要索引每个键,这会导致插入非常慢,还会占用很多空间,并且很可能对查询速度提升不大。

创建索引时要考虑如下问题

  1. 会做什么样的查询?其中那些需要索引
  2. 每个键的索引方向
  3. 如何应对扩展?有没有种不同的键的排列可以使常用数据更多的保留在内存中。

    索引内嵌文档中的键

> db.trans.ensureIndex({"comments.author": 1})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 3,
    "numIndexesAfter" : 4,
    "ok" : 1
}

为排序创建索引
如果对没有索引的键调用sort,MongoDB需要将所有数据提取到内存来排序,可能会是内存不够,如果对要有索引的键调用sort就可以按顺序加入到内存,就能排序大规模的数据。

索引名称
集合中每个索引都有一个字符串类型的名字,来标识索引,服务器通过这个名字操作索引。默认情况下,索引名称是keyname1_dir1_keyname2_dir2_…_keynameN_dirN这种形式,其中keynameX代表索引的键,dirX代表索引的方向。
自定义索引名字:

> db.trans.ensureIndex({date: 1}, {name: "aa"})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 4,
    "numIndexesAfter" : 5,
    "ok" : 1
}

唯一索引
唯一索引可以确保集合的每一个文档的指定键都有唯一值。

> db.trans.ensureIndex({date: 1, card1: 1}, {uniq: true})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 5,
    "numIndexesAfter" : 6,
    "ok" : 1
}

默认情况下,insert并不检查文档是否插入过了,所以,为了避免插入的文档中包含与唯一键重复的值,可能要用安全插入才能满足要求, 这样,在插入这样的文档时会看到存在重复键错误的提示。

消除重复

> db.trans.ensureIndex({card2: 1}, {"uniq": true, "dropDups": true})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 6,
    "numIndexesAfter" : 7,
    "ok" : 1
}

符合唯一索引,单个键的值可以相同,所有键组合起来不同就好。

使用explain和hint

explian会获得查询方面诸多有用的信息。只要对游标调用该方法,就可以得到查询细节。explain会返回一个文档,而不是游标本身。

> db.trans.find({"card1" : "621773"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "mongo_test.trans",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "card1" : {
                "$eq" : "621773"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "card1" : 1
                },
                "indexName" : "card1_1",
                "isMultiKey" : false,
                "direction" : "forward",
                "indexBounds" : {
                    "card1" : [
                        "[\"621773\", \"621773\"]"
                    ]
                }
            }
        },
        "rejectedPlans" : [
            {
                "stage" : "FETCH",
                "inputStage" : {
                    "stage" : "IXSCAN",
                    "keyPattern" : {
                        "card1" : 1,
                        "card2" : 1
                    },
                    "indexName" : "card1_1_card2_1",
                    "isMultiKey" : false,
                    "direction" : "forward",
                    "indexBounds" : {
                        "card1" : [
                            "[\"621773\", \"621773\"]"
                        ],
                        "card2" : [
                            "[MinKey, MaxKey]"
                        ]
                    }
                }
            }
        ]
    },
    "serverInfo" : {
        "host" : "upsmart",
        "port" : 27017,
        "version" : "3.0.3",
        "gitVersion" : "b40106b36eecd1b4407eb1ad1af6bc60593c6105"
    },
    "ok" : 1
}

使用hint强制使用某个索引

> db.trans.find({"card1" : "621773"}).hint({card1: 1}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "mongo_test.trans",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "card1" : {
                "$eq" : "621773"
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",
                "keyPattern" : {
                    "card1" : 1
                },
                "indexName" : "card1_1",
                "isMultiKey" : false,
                "direction" : "forward",
                "indexBounds" : {
                    "card1" : [
                        "[\"621773\", \"621773\"]"
                    ]
                }
            }
        },
        "rejectedPlans" : [ ]
    },
    "serverInfo" : {
        "host" : "upsmart",
        "port" : 27017,
        "version" : "3.0.3",
        "gitVersion" : "b40106b36eecd1b4407eb1ad1af6bc60593c6105"
    },
    "ok" : 1
}

索引管理

索引的元信息存储在每个数据库的system.index集合中。这是一个保留集合,不能对其插入或删除文档。操作只能通过ensureIndex或者dropIndexes进行。

建立索引可以在后台进行,同时正常处理请求,要是不包括background这个选项,数据库会阻塞建立索引期间的所有请求

> db.employees.ensureIndex({firstername: 1},{background: true})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

删除索引:

> db.runCommand({dropIndexes: "employees", index: "firstername_1"})
{ "nIndexesWas" : 2, "ok" : 1 }

地理空间索引

有一种查询变得越来越流行: 找到离当前位置最近的N个场所。MongoDB为坐标平面查询提供了专门的索引,称作地理空间索引。
地理空间索引同样可以由ensureIndex来创建,只不过参数不是1或者-1,而是”2d”;

> db.map.ensureIndex({"gps": "2d"})
{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1
}

“gps”键的值必须是某种形式的一对值:一个包含两个元素的数组或是包含两个键的内嵌文档。

地理空间查询以两种方式进行,即普通查询或者使用数据库命令。

> db.map.find({gps: {$near: [20,10]}})
{ "_id" : ObjectId("57a47024ac1c6b4c5f65869f"), "gps" : [ 2, 39 ] }
{ "_id" : ObjectId("57a464faac1c6b4c5f65869e"), "gps" : [ 0, 100 ] }
> db.runCommand({geoNear: "map", near: [10,30] , limit:1})
{
    "results" : [
        {
            "dis" : 12.041594578792296,
            "obj" : {
                "_id" : ObjectId("57a47024ac1c6b4c5f65869f"),
                "gps" : [
                    2,
                    39
                ]
            }
        }
    ],
    "stats" : {
        "nscanned" : 1,
        "objectsLoaded" : 1,
        "avgDistance" : 12.041594578792296,
        "maxDistance" : 12.041594578792296,
        "time" : 0
    },
    "ok" : 1
}

MongoDB不但能找到靠近的一个点的文档,还能找到指定形状内的文档。做法就是将原来的” near"" within”。

MongoDB Enterprise > db.map.find({gps: {$within: {$box: [[2, 4], [20, 50]]}}})
{ "_id" : ObjectId("57a5b0d254330f9fbc585fa5"), "gps" : [ 3, 40 ] }

box center”来找到圆形内部的所有点,只不过参数变成圆心和半径。

MongoDB Enterprise > db.map.find({gps: {$within: {$center: [[0,0],50]}}})
{ "_id" : ObjectId("57a5b0d254330f9fbc585fa5"), "gps" : [ 3, 40 ] }

聚合

MongoDB除了基本的查询功能,还提供了很多强大的聚合工具,其中简单的可计算集合中的文档个数,复杂的可利用MapReduce做复杂的数据分析。
count
count是最简单的聚合工具,返回集合中的文档个数

> db.map.count()
4
> db.map.find().count()
4

distinct
distinct用来找出给定键的所有不同的值。使用时必须指定集合和键。

MongoDB Enterprise > db.runCommand({distinct: "foo", key: "age"})
{
        "waitedMS" : NumberLong(0),
        "values" : [
                10
        ],
        "stats" : {
                "n" : 5,
                "nscanned" : 0,
                "nscannedObjects" : 5,
                "timems" : 0,
                "planSummary" : "COLLSCAN"
        },
        "ok" : 1

group
group先选定分组依据的键,而后Mongodb就会将集合依据选定键值的不同分成若干组,然后,可以通过聚合每一组内的文档,产生一个结果文档。

MongoDB Enterprise > db.runCommand({group: {
... ns: "foo",
... key: "name",
... initial: {age: 0},
... $reduce: function(doc, prev) {
...     if(doc.age > prev.age) {
...       prev.age = doc.age
...    }
... }
... }})
{
        "waitedMS" : NumberLong(0),
        "retval" : [
                {
                        "age" : 10
                }
        ],
        "count" : NumberLong(6),
        "keys" : NumberLong(1),
        "ok" : 1
}

还可以在group中添加condition过滤一些文档。

> db.runCommand({group: { ns: "day_log", key: {"date": true,"
ieie": true}, initial: {"count": 0}, $reduce: function(doc, prev) {  prev.count
+= 1; }, condition: {date: {$gt: "2014/11/1"}} }})

使用完成器用于精简从数据库查到用户的数据。

> db.runCommand({group: { ns: "day_log", key: {"date": true},
 initial: {"ieies": {}}, $reduce: function(doc, prev) { if(doc.ieie in prev.ieie
s){prev.ieies[doc.ieie]++;} else { prev.ieies[doc.ieie] = 1;} }, condition: {dat
e: {$gt: "2014/11/1"}}, finalize: function(prev) {  var max = 0;  for ( i in pre
v.ieies) {    if (prev.ieies[i] > max) {     prev.ieie = i;     max = prev.ieies
[i];    } } delete prev.ieies; }}})

有时候分组所依据的条件非常复杂,可以将函数作为键使用

MongoDB Enterprise > db.runCommand({group: { ns: "day_log", $keyf: function(x){
return {date: x.date.substring(0,9)}; }, initial: {"ieies": {}}, $reduce: functi
on(doc, prev) { if(doc.ieie in prev.ieies){prev.ieies[doc.ieie]++;} else { prev.
ieies[doc.ieie] = 1;} }, condition: {date: {$gt: "2014/11/1"}}, finalize: functi
on(prev) {  var max = 0;  for ( i in prev.ieies) {    if (prev.ieies[i] > max) {
     prev.ieie = i;     max = prev.ieies[i];    } } delete prev.ieies; }}})

注意function返回的必须是一个对象。
MapReduce

使用MapReduce的代价就是速度: group不是很快,Mapreduce更慢,绝不要用在“实时”环境中。要作为后台任务来运行Mapreduce,将创建一个保存结果的集合,可以对这个集合进行实时查询。

> map = function() { emit(this.date, this.ieie); }
function () { emit(this.date, this.ieie); }
> reduce = function(key, emits) { value_map= new Map(); count
 = 0; for (i in emits) {  if(value_map[i]==null) { value_map.put(i,0);   count++
;} } return {"count": count}; }
> mr = db.runCommand({mapreduce: "day_log", "map": map,"reduc
e": reduce, "out": "sss"})
{
        "result" : "sss",
        "timeMillis" : 335,
        "counts" : {
                "input" : 2303,
                "emit" : 2303,
                "reduce" : 204,
                "output" : 61
        },
        "ok" : 1
}

有时候不包含out时会报 “‘out’ has to be a string or an object”的错误。
reduce一定要能反复调用,不论是映射环节还是前一个简化环节,所以reduce返回文档必须能做为reduce的第二个参数的一个元素。reduce应该能处理emit文档和reduce结果的各种组合。

mapreduce可选的键:

  • “finalize”: 函数
    将reduce结果发送给这个键,这是处理的最后一步。
  • “query”: 文档
    会在发往map函数前,先用指定条件过滤文档
  • “sort”:文档
    会在发往map前先给文档排序(与limit一同使用非常有用)
  • “limit”:整数
    发往map函数的文档数量上限。
  • “scope”: 文档
    JavaScript代码中要用到的变量。
  • “verbose”: 布尔
    是否产生更加详尽的服务器日志。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值