mongoDB通过_id删除doc

根据mongodb数据记录里面的_id字段删除相应的docs,通过下面代码进行删除时,并不能删除成功

代码如下:

var ObjectId = require('mongodb').ObjectId;
db.collection('infochanges').remove({"_id":{"_id":ObjectId(idvalue)}).then(function(){})

报错如下:

TypeError:Cannot convert undefined or null to object

解决方法:

使用findAndRemove,代码如下:

db.collection('infochanges').findAndRemove({"_id":ObjectId(index)}).then(function(){})

通过_id删除docs要用findAndRemove,remove不起作用

使用Vertx+Ignite+MongoDB搭建大DAU游戏服务器

最近在funplus做游戏,进而研究了一个新型架构。

之前做游戏都是自己使用java搭建架构,经过几年的积累确实也达到了最初的设想,多进程,进程内多线程,无锁,0延迟纯jdbc写库。对于单服架构来说,已经趋近于极致。

今年小游戏盛行,如海盗来了,疯狂游戏那家公司,全部使用的都是go+mongodb实现的,因为go的语言级别支援高并发,这点是java无法比拟的。不过java开源项目多,有很多的高手铺垫了超多的框架,比如vertx,akka都可以更加充分的释放java的能力。就看使用者的认识水平了。

本次选择vertx,主要是其在网络通讯这块,对netty的包装,加上自己的eventloop模型,使得响应web请求速度基本属于前3的水平。

netServer = vertx.createHttpServer(httpServerOptions);
        netServer.requestHandler();
        netServer.requestHandler(hs -> {
            if (hs.path().equals("/ping")) {
                hs.response().end("pong");
                return;
            }
            hs.response().close();
            return;
        });

        netServer.websocketHandler(ws -> {
            if (!ws.path().equals("/" + wsname)) {
                ws.reject();
                return;
            }
            Player player = new Player(ws, ws.binaryHandlerID());
            players.put(player.getConnId(), player);
            player.setServerUUID(gateAdress);
            //日志
            if (log.isDebugEnabled()) {
                SocketAddress addrLocal = ws.localAddress();
                log.debug("新建一个连接:连接ID={},  本地端口={}, 远程地址={}", player.getconnId(), addrLocal.port(), ws.remoteAddress());
            }
            //有连接过来了
            ws.binaryMessageHandler(data -> {
                int readableBytes = data.length();
                if (readableBytes < IMessage.MIN_MESSAGE_LENGTH) {
                    return;
                }
                int len = data.getShort(0);
                if (len > 64 * 1024) {
                    log.error("conn:" + player.getId() + "  发送数据包过大:" + len);
                    return;
                }
                if (readableBytes < len) {
                    return;
                }

                CGMessage msg = decode(data);
                if (msg == null) return;
                inputHandler(msg, player);
            });
            ws.exceptionHandler(e -> {
                if (e.getMessage().equals("WebSocket is closed")) {
//                    player.disconnect();
                }
                //断连的日志就不打印堆栈了
                if (e.getMessage().contains("Player reset by peer") || e.getMessage().contains("远程主机强迫关闭了一个现有的连接")) {
                    log.error("主动断开:connId={},cause={}", player.getconnId(), e.getCause());
                } else {
                    //输出错误日志
                    log.error("发生异常:connId={},cause={}", player.getconnId(), e.getCause());
                }
            });
            ws.closeHandler(t -> {
//                if (player == null) return;
                //连接状态
                //日志
                if (log.isDebugEnabled()) {
                    log.debug("连接关闭:connId={}, status={}", player.getconnId(), player == null ? "" : player.toString());
                }
                if (player.getState() == PlayerState.connected || player.getState() == PlayerState.init || player.getState() == PlayerState.logouting) {
                    player.setState(PlayerState.logouted);
                    //Remove掉 session connId = Player
                    //删掉连接对应的player
                    players.remove(player.getConnId());
                    return;
                }
                if (player.getUserInfo() == null) {
                    //删掉连接对应的player
                    players.remove(player.getConnId());
                    return;
                }
                gateService.closePlayer(player.getconnId(), ar -> {
                    if (ar.failed()) {
                        Loggers.coreLogger.error("player connId:" + player.getconnId() + " 离线退出异常!!!" + ar.cause().getMessage());
                    }
                    //删掉连接对应的player
                    players.remove(player.getConnId());
                });

            });
        }).listen(port, host, res -> {
            if (res.succeeded()) {
                //启动日志信息
                log.info(" started. Listen: " + port + "  vert:" + vertx.hashCode());
                future.complete();
            }
        });

vertx能方便的使用eventloop线程池响应玩家发来的请求,并永远在特定线程进行代码调用。

比自己使用hash线程池靠谱很多。ps. 自己造轮子不是不好,主要实现方法不一定测试完整,有意想不到的情况,就要自己来趟坑。

后面主要是说一下,但如果大规模请求MongoDB,需要更高的MongoDB响应要求。进而想到要加缓存机制,最初想到的是redis+mongodb,自己实现读通过,写通过。
如果redis不存在,则从mongodb读取,并放入缓存,写数据先写缓存,后写mongodb。

自己实现的这种存储机制,比较low。所以继续寻找缓存方案。

过程中,发现了一个曝光率不高的框架,也就是Apache Ignite。最新一代数据网格。

关键的一步,就是如果让vertx与Ignite工作到一起。这是一个必要的条件。

package cn.empires;

import cn.empires.common.Globals;
import cn.empires.common.contants.Loggers;
import cn.empires.gs.support.observer.Event;
import cn.empires.verticle.OnlineVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Launcher;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.json.JsonObject;

public class MainLaunch extends Launcher {

    private JsonObject config;

    public static void main(String[] args) {
        System.setProperty("logFileName", "gateServer");
        new MainLaunch().dispatch(args);
    }

    @Override
    protected String getDefaultCommand() {
        return super.getDefaultCommand();
    }

    @Override
    protected String getMainVerticle() {
        return "cn.empires.verticle.GateVerticle";
    }

    @Override
    public void afterConfigParsed(JsonObject config) {
        super.afterConfigParsed(config);
        this.config = config;
    }

    @Override
    public void beforeStartingVertx(VertxOptions options) {
        options.setClustered(true);
    }

    @Override
    public void afterStartingVertx(Vertx vertx) {
        super.afterStartingVertx(vertx);
        //config.put("redis.password", "123456");
        //初始化全局相关信息
        ListenerInit.init(Event.instance);
        Loggers.coreLogger.info("Globals init .");
        Globals.init(vertx, config);
        vertx.deployVerticle(OnlineVerticle.class, new DeploymentOptions().setConfig(config));
    }

    @Override
    public void beforeDeployingVerticle(DeploymentOptions deploymentOptions) {
        super.beforeDeployingVerticle(deploymentOptions);
    }

    @Override
    public void beforeStoppingVertx(Vertx vertx) {
        super.beforeStoppingVertx(vertx);
    }

    @Override
    public void afterStoppingVertx() {
        super.afterStoppingVertx();
    }

    @Override
    public void handleDeployFailed(Vertx vertx, String mainVerticle, DeploymentOptions deploymentOptions, Throwable cause) {
        super.handleDeployFailed(vertx, mainVerticle, deploymentOptions, cause);
    }

}

如果想使用Ignite的缓存,必须需要Ignite实例对象。否则无法获取。

if (ignite == null) {
     ClusterManager clusterManager = ((VertxInternal) vertx).getClusterManager();
     String uuid = clusterManager.getNodeID();
     ignite = Ignition.ignite(UUID.fromString(uuid));
}

在classpath中,配置一个ignite.xml,vertx启动的时候自动会加载ignite.xml,然后使用IgniteManager进行集群管理。
我只贴一遍ignite.xml配置

<?xml version="1.0" encoding="UTF-8"?>

<!--
  Licensed to the Apache Software Foundation (ASF) under one or more
  contributor license agreements.  See the NOTICE file distributed with
  this work for additional information regarding copyright ownership.
  The ASF licenses this file to You under the Apache License, Version 2.0
  (the "License"); you may not use this file except in compliance with
  the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->

<!--
    Ignite Spring configuration file to startup Ignite cache.

    This file demonstrates how to configure cache using Spring. Provided cache
    will be created on node startup.

    Use this configuration file when running HTTP REST examples (see 'examples/rest' folder).

    When starting a standalone node, you need to execute the following command:
    {IGNITE_HOME}/bin/ignite.{bat|sh} examples/config/example-cache.xml

    When starting Ignite from Java IDE, pass path to this file to Ignition:
    Ignition.start("examples/config/example-cache.xml");
-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="dataStorageConfiguration">
            <bean class="org.apache.ignite.configuration.DataStorageConfiguration">
                <!-- Set the page size to 4 KB -->
                  <property name="pageSize" value="4096"/>
                <!-- Set concurrency level -->
                  <property name="concurrencyLevel" value="6"/>
                  <property name="systemRegionInitialSize" value="#{40 * 1024 * 1024}"/>
                  <property name="systemRegionMaxSize" value="#{80 * 1024 * 1024}"/>
                  <property name="defaultDataRegionConfiguration">
                    <bean class="org.apache.ignite.configuration.DataRegionConfiguration">
                        <property name="name" value="Default_Region"/>
                        <!-- 设置默认内存区最大内存为 512M. -->
                        <property name="maxSize" value="#{512L * 1024 * 1024}"/>
                        <!-- Enabling RANDOM_LRU eviction for this region.  -->
                            <property name="pageEvictionMode" value="RANDOM_2_LRU"/>
                    </bean>
                </property>
                <property name="dataRegionConfigurations">
                    <list>
                      <!--
                          Defining a data region that will consume up to 500 MB of RAM and 
                          will have eviction and persistence enabled.
                      -->
                      <bean class="org.apache.ignite.configuration.DataRegionConfiguration">
                        <!-- Custom region name. -->
                        <property name="name" value="500MB_Region"/>

                        <!-- 100 MB initial size. -->
                        <property name="initialSize" value="#{100L * 1024 * 1024}"/>

                        <!-- 500 MB maximum size. -->
                        <property name="maxSize" value="#{500L * 1024 * 1024}"/>

                        <!-- Enabling RANDOM_LRU eviction for this region.  -->
                            <property name="pageEvictionMode" value="RANDOM_2_LRU"/>
                      </bean>
                    </list>
                </property>
            </bean>
        </property>
        <property name="cacheConfiguration">
            <list>
                   <bean class="org.apache.ignite.configuration.CacheConfiguration">
                           <property name="name" value="UserInfo"/>
                           <property name="cacheMode" value="PARTITIONED"/>
                           <property name="atomicityMode" value="ATOMIC"/>
                           <property name="backups" value="0"/>
                           <property name="cacheStoreFactory">
                               <bean class="javax.cache.configuration.FactoryBuilder" factory-method="factoryOf">
                                   <constructor-arg value="cn.empires.common.cache.UserCacheStore"/>
                               </bean>
                           </property>
                           <property name="readThrough" value="true"/>
                           <property name="writeThrough" value="true"/>
                           <property name="writeBehindEnabled" value="true"/>
                           <property name="writeBehindFlushSize" value="1024"/>
                           <property name="writeBehindFlushFrequency" value="5"/>
                           <property name="writeBehindFlushThreadCount" value="1"/>
                           <property name="writeBehindBatchSize" value="512"/>
                           <property name="dataRegionName" value="Default_Region"/>
                </bean>
            </list>
        </property>
        <property name="failureDetectionTimeout" value="60000"/>
        <!-- Explicitly configure TCP discovery SPI to provide list of initial nodes. -->
        <property name="discoverySpi">
            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
                <property name="ipFinder">
                    <!--
                        Ignite provides several options for automatic discovery that can be used
                        instead os static IP based discovery. For information on all options refer
                        to our documentation: http://apacheignite.readme.io/docs/cluster-config
                    -->
                    <!-- Uncomment static IP finder to enable static-based discovery of initial nodes. -->
                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
                    <!-- <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.multicast.TcpDiscoveryMulticastIpFinder"> -->
                        <property name="addresses">
                            <list>
                                <!-- In distributed environment, replace with actual host IP address. -->
                                <value>127.0.0.1:47500..47509</value>
                            </list>
                        </property>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>

Ignite 对内存有细致的划分,可以分多个区域Region,每个区域有自己的配置,比如设置初始大小和最大大小,以及淘汰策略。
UserInfo对应的CacheConfiguration对Cache使用进行了配置,比如readThrough writeThrough writeBehindEnabled等等,细致的配置诸如后写刷新频率writeBehindFlushFrequency为5,表示5秒才会刷新一次更新数据。

    public static <T> IgniteCache<String, T> createIgniteCache(String cacheName, Class<? extends CacheStoreAdapter<String, T>> clazz) {
        CacheConfiguration<String, T> cacheCfg = new CacheConfiguration<>(cacheName);
        return Globals.ignite().getOrCreateCache(cacheCfg);
    }

在Globals工具类,提供工具方法获得IgniteCache对象。

package cn.empires.gs.player.service.impl;

import org.apache.ignite.IgniteCache;
import org.apache.ignite.lang.IgniteFuture;

import cn.empires.common.Globals;
import cn.empires.common.cache.UserCacheStore;
import cn.empires.common.service.ServiceBase;
import cn.empires.gs.model.UserInfo;
import cn.empires.gs.player.service.UserService;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;

public class UserServiceImpl extends ServiceBase implements UserService {

    private final IgniteCache<String, UserInfo> cache;

    public UserServiceImpl(Vertx vertx, JsonObject config) {
        super(vertx, config);
        cache = Globals.createIgniteCache(UserInfo.tableName, UserCacheStore.class);
    }

    @Override
    public UserService getUserInfo(String id, Handler<AsyncResult<UserInfo>> handler) {
        IgniteFuture<UserInfo> future = cache.getAsync(id);
        future.listen(h -> {
            if(h.isDone()) {
                handler.handle(Future.succeededFuture(h.get()));
            }
        });        
        return this;
    }


    @Override
    public UserService saveUserInfo(UserInfo userInfo, Handler<AsyncResult<UserInfo>> handler) {
        IgniteFuture<Void> future = cache.putAsync(userInfo.get_id(), userInfo);
        future.listen(h -> {
            if(h.isDone()) {
                handler.handle(Future.succeededFuture(userInfo));
            }
        });
        return this;
    }

}

最后一件事,就是同步写库,可以读通过从MongoDB进行读取。

package cn.empires.common.cache;

import java.util.ArrayList;
import java.util.List;

import javax.cache.Cache.Entry;
import javax.cache.integration.CacheLoaderException;
import javax.cache.integration.CacheWriterException;

import org.apache.ignite.IgniteException;
import org.apache.ignite.cache.store.CacheStoreAdapter;
import org.apache.ignite.lifecycle.LifecycleAware;
import org.bson.Document;

import com.mongodb.Block;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.UpdateOptions;

import cn.empires.common.Globals;
import cn.empires.common.contants.Loggers;
import cn.empires.gs.model.UserInfo;
import io.vertx.core.json.JsonObject;

public class UserCacheStore extends CacheStoreAdapter<String, UserInfo> implements LifecycleAware {

    /** Mongo collection. */
    private MongoCollection<Document> collection;

    @Override
    public void start() throws IgniteException {
    }

    @Override
    public UserInfo load(String key) throws CacheLoaderException {
        if(collection == null) {
            collection = Globals.mongoDb().getCollection(UserInfo.tableName);
        }
        FindIterable<Document> iter = collection.find(Filters.eq("_id", key));
        final List<JsonObject> result = new ArrayList<>(1);
        iter.forEach(new Block<Document>() {
            public void apply(Document _doc) {
                result.add(new JsonObject(_doc.toJson()));
            }
        });
        if(result != null && !result.isEmpty()) {
            Loggers.userLogger.info("CacheStore load UserInfo.");
            JsonObject jsonObj = result.get(0);
            return UserInfo.fromDB(jsonObj);
        }
        return null;
    }

    @Override
    public void write(Entry<? extends String, ? extends UserInfo> entry) throws CacheWriterException {
        if(collection == null) {
            collection = Globals.mongoDb().getCollection(UserInfo.tableName);
        }
        Document filter = new Document();
        filter.append("_id", entry.getKey());

        Document replacement = new Document();
        replacement.append("value", entry.getValue().toString());
        collection.replaceOne(filter, replacement, new UpdateOptions().upsert(true));
        Loggers.userLogger.info("CacheStore saved UserInfo.");
    }

    @Override
    public void delete(Object key) throws CacheWriterException {

    }



    @Override
    public void stop() throws IgniteException {

    }

}

由于在ignite.xml中进行了配置

<bean class="javax.cache.configuration.FactoryBuilder" factory-method="factoryOf">
    <constructor-arg value="cn.empires.common.cache.UserCacheStore"/>
</bean>

所以在使用Cache获取UserInfo的时候,如果不存在对应的信息,就会从MongoDB读取。

如何在MongoDB中的$match中使用聚合运算符(例如$year或$dayOfMonth)?

我拥有一个包含create_date属性的文档的集合.我想通过汇总管道发送这些文件,对他们做一些工作.理想情况下,我想在使用$match进行任何其他工作之前使用$match进行过滤,以便我可以利用索引,但是我无法弄清楚如何在我的新的$year / $month / $dayOfMonth操作符中使用$match表达式.
有一些例子浮动了如何在$项目操作中使用运算符,但我担心,通过将$项目作为我的管道中的第一步,我没有访问我的索引(MongoDB文档表明第一个表达式必须是$match以利用索引).

样品数据:

{
    post_body: 'This is the body of test post 1',
    created_date: ISODate('2012-09-29T05:23:41Z')
    comments: 48
}
{
    post_body: 'This is the body of test post 2',
    created_date: ISODate('2012-09-24T12:34:13Z')
    comments: 10
}
{
    post_body: 'This is the body of test post 3',
    created_date: ISODate('2012-08-16T12:34:13Z')
    comments: 10
}

我想通过一个汇总管道运行它,以获得9月份所有帖子的总评论

{
    aggregate: 'posts',
    pipeline: [
         {$match:
             /*Can I use the $year/$month operators here to match Sept 2012?
             $year:created_date : 2012,
             $month:created_date : 9
             */
             /*or does this have to be 
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt: {$date:'2012-10-01T04:00:00Z'} }
             */
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

这是有效的,但匹配失去对更复杂查询的任何索引的访问:

{
    aggregate: 'posts',
    pipeline: [
         {$project:
              {
                   month : {$month:'$created_date'},
                   year : {$year:'$created_date'}
              }
         },
         {$match:
              {
                   month:9,
                   year: 2012
               }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

如您已经找到的,您不能在文档中不匹配的字段(它的工作方式与查找工作方式完全相同),如果您首先使用$project,那么您将失去使用索引的能力.
您可以做的是将您的努力结合如下:

{
    aggregate: 'posts',
    pipeline: [
         {$match: {
             created_date : 
                  {$gte:{$date:'2012-09-01T04:00:00Z'}, 
                  $lt:  {date:'2012-10-01T04:00:00Z'} 
                  }}
             }
         },
         {$group:
             {_id: '0',
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

以上只给出了9月的聚合,如果你想聚合多个月,你可以举个例子:

{
    aggregate: 'posts',
    pipeline: [
         {$match: {
             created_date : 
                  { $gte:'2012-07-01T04:00:00Z', 
                    $lt: '2012-10-01T04:00:00Z'
                  }
         },
         {$project: {
              comments: 1,
              new_created: {
                        "yr" : {"$year" : "$created_date"},
                        "mo" : {"$month" : "$created_date"}
                     }
              }
         },
         {$group:
             {_id: "$new_created",
              totalComments:{$sum:'$comments'}
             }
          }
    ]
 }

你会得到如下结果:

{
    "result" : [
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 7
            },
            "totalComments" : 5
        },
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 8
            },
            "totalComments" : 19
        },
        {
            "_id" : {
                "yr" : 2012,
                "mo" : 9
            },
            "totalComments" : 21
        }
    ],
    "ok" : 1
}

Mongodb启动方法:设定参数启动;从设置文件启动

接手的MongoDB只有一个日志文件,体积非常大,排错不便。在找解决办法的时候发现MongoDB的启动文件配置项超级多,于是产生了解释配置参数的想法。

mongod服务有两种启动方式

一种是通过配置文件
$ ./mongod -f /app/mongodb/mongodb27017/conf/mongodb.conf

一种直接指明参数
$./mongod --dbpath=/app/mongodb/db --port=27017 --fork --logpath=/app/mongodb/db/mongodb.log

由于安装文件没有默认的配置文件,需要配置的参数项又比较多,推荐使用配置文件的方式启动服务。下面详细介绍配置

storage:
    dbPath: "/data/mongodb/data”   #数据目录
    directoryPerDB: true      #将不同DB的数据分子目录存储,基于dbPath,默认为 false
    engine: “wiredTiger"      #存储引擎,3.2后默认wiredTiger 可选 mmapv1
    wiredTiger:
        engineConfig:
            cacheSizeGB: 15     #Mongodb吃内存,并且不会主动释放,默认的缓存大小为max(1/2maxmem,256M),可参照系统总内存进行设置。
            journalCompressor: snappy     #journal日志的压缩算法,可选值为“none”、“snappy”、“zlib”。压缩差别可百度,总体来说snappy最合适。
            directoryForIndexes: true #是否将索引和collections数据分别存储在dbPath单独的目录中。默认值为false,放在一个目录。
        collectionConfig:
            blockCompressor: snappy      #collection数据压缩算法,可选值“none”、“snappy”、“zlib”
        indexConfig:
            prefixCompression: true   #是否对索引数据使用“前缀压缩” ,对那些经过排序的值存储,可以减少索引数据的内存使用量。默认值为true。
    journal:
        enabled: true     #是否开启journal日志持久存储,journal日志用来数据恢复,是mongod最基础的特性,通常用于故障恢复。
        commitIntervalMs: 100   #New in version 3.2.  日志提交间隔
systemLog:
    destination: file  #日志输出目的地,可为 file 或 syslog; if file, you must also specify systemLog.path  
    path: "/var/log/mongodb/mongodb.log"
    logAppend: true   #启动或重启后是否追加写入
    logRotate: rename   #防止一个日志文件特别大,可选项:rename(重命名日志文件,默认值);reopen(使用linux日志rotate特性,关闭并重新打开此日志文件,可以避免日志丢失,但是logAppend必须为true)
    timeStampFormat: ctime  #时间格式 默认为 iso8601-local
replication:
     oplogSizeMB: 10240
     replSetName: getui-bi
     enableMajorityReadConcern: false
processManagement:
    fork: true    #守护进程模式启动,默认 false
    pidFilePath: "/var/run/mongodb/mongod.pid”  #配合"fork:true"参数,将mongod/mongos进程ID写入指定的文件,如果不指定,将不会创建PID文件
net:
    bindIp: 127.0.0.1  
    port: 27017
    ipv6: false   #是否支持mongos/mongod多个实例之间使用IPV6网络,默认值为false。此值需要在整个cluster中保持一致。
    maxIncomingConnections: 10000   #进程允许的最大连接数,默认:65536
    wireObjectCheck : false  #当客户端写入数据时,mongos/mongod是否检测数据的有效性(BSON),如果数据格式不良,此insert、update操作将会被拒绝;默认值为true
    unixDomainSocket:
        enabled : true
security:
    keyFile: /opt/mongodb/etc/mongodb-keyfile #指定分片集或副本集成员之间身份验证的key文件存储位置。
    authorization: enabled   #打开访问数据库和进行操作的用户角色认证

回到写这篇博客的原因,Mongodb的日志量较大,可通过上述rename的方式进行日期分类,也可设置日志级别和安静模式,使日志量减小。

systemLog:         #系统日志配置
   verbosity: <int> #日志级别,0:默认值,包含“info”信息,1~5,即大于0的值均会包含debug信息
   quiet: <boolean>  #"安静",此时mongod/mongos将会尝试减少日志的输出量。不建议在production环境下开启,否则将会导致跟踪错误比较困难。 

python操作mongodb根据_id查询数据的实现方法

本文实例讲述了python操作mongodb根据_id查询数据的实现方法。分享给大家供大家参考。具体分析如下:

_id是mongodb自动生成的id,其类型为ObjectId,所以如果需要在python中通过_id查询,就需要转换类型

如果pymongo的版本号小于2.2,使用下面的语句导入ObjectId

from pymongo.objectid import ObjectId

如果pymongo的版本号大于2.2,则使用下面的语句

from bson.objectid import ObjectId

查询代码如下:

collection.find_one({'_id':ObjectId('50f0d76347f4ec148890ef1e')})

MongoDB学习笔记(7)— 条件操作符

描述

条件操作符用于比较两个表达式并从mongoDB集合中获取数据。

在本章节中,我们将讨论如何在MongoDB中使用条件操作符。

MongoDB中条件操作符有:

  • (>) 大于 – $gt
  • (<) 小于 – $lt
  • (>=) 大于等于 – $gte
  • (<= ) 小于等于 – $lte

我们使用的数据库名称为”runoob” 我们的集合名称为”col”,以下为我们插入的数据。

为了方便测试,我们可以先使用以下命令清空集合 “col” 的数据:

db.col.remove({})

插入以下数据

>db.col.insert({
    title: 'PHP 教程', 
    description: 'PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: ['php'],
    likes: 200
})

>db.col.insert({title: 'Java 教程', 
    description: 'Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: ['java'],
    likes: 150
})

>db.col.insert({title: 'MongoDB 教程', 
    description: 'MongoDB 是一个 Nosql 数据库',
    by: '菜鸟教程',
    url: 'http://www.runoob.com',
    tags: ['mongodb'],
    likes: 100
})

使用find()命令查看数据:

> db.col.find()
{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

MongoDB (>) 大于操作符 – $gt

如果你想获取 “col” 集合中 “likes” 大于 100 的数据,你可以使用以下命令:

db.col.find({likes : {$gt : 100}})

类似于SQL语句:

Select * from col where likes > 100;

输出结果:

> db.col.find({likes : {$gt : 100}})
{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }
>

MongoDB(>=)大于等于操作符 – $gte

如果你想获取”col”集合中 “likes” 大于等于 100 的数据,你可以使用以下命令:

db.col.find({likes : {$gte : 100}})

类似于SQL语句:

Select * from col where likes >=100;

输出结果:

> db.col.find({likes : {$gte : 100}})
{ "_id" : ObjectId("56066542ade2f21f36b0313a"), "title" : "PHP 教程", "description" : "PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "php" ], "likes" : 200 }
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }
>

MongoDB (<) 小于操作符 – $lt

如果你想获取”col”集合中 “likes” 小于 150 的数据,你可以使用以下命令:

db.col.find({likes : {$lt : 150}})

类似于SQL语句:

Select * from col where likes < 150;

输出结果:

> db.col.find({likes : {$lt : 150}})
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

MongoDB (<=) 小于操作符 – $lte

如果你想获取”col”集合中 “likes” 小于等于 150 的数据,你可以使用以下命令:

db.col.find({likes : {$lte : 150}})

类似于SQL语句:

Select * from col where likes <= 150;

输出结果:

> db.col.find({likes : {$lte : 150}})
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }
{ "_id" : ObjectId("5606654fade2f21f36b0313c"), "title" : "MongoDB 教程", "description" : "MongoDB 是一个 Nosql 数据库", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "mongodb" ], "likes" : 100 }

MongoDB 使用 (<) 和 (>) 查询 – $lt 和 $gt

如果你想获取”col”集合中 “likes” 大于100,小于 200 的数据,你可以使用以下命令:

db.col.find({likes : {$lt :200, $gt : 100}})

类似于SQL语句:

Select * from col where likes>100 AND  likes<200;

输出结果:

> db.col.find({likes : {$lt :200, $gt : 100}})
{ "_id" : ObjectId("56066549ade2f21f36b0313b"), "title" : "Java 教程", "description" : "Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言。", "by" : "菜鸟教程", "url" : "http://www.runoob.com", "tags" : [ "java" ], "likes" : 150 }
>

MongoDB 查询文档 http://www.runoob.com/mongodb/mongodb-query.html
MongoDB $type 操作符 http://www.runoob.com/mongodb/mongodb-operators-type.html

1、一些简写说明:

$gt -------- greater than  >

$gte --------- gt equal  >=

$lt -------- less than  <

$lte --------- lt equal  <=

$ne ----------- not equal  !=

$eq  --------  equal  =

2、模糊查询

查询 title 包含”教”字的文档:

db.col.find({title:/教/})

查询 title 字段以”教”字开头的文档:

db.col.find({title:/^教/})

查询 titl e字段以”教”字结尾的文档:

db.col.find({title:/教$/})

MongoDB 在系统数据库local上无法创建用户的解决方法

我们知道,MongoDB的Oplog (operations log)记录了用户的最近一段时间的操作(时间长短主要受设置的oplogSize和程序的写入更新量的影响)。那么,如果其他部门(例如BI团队)需要抽取数据,从 local.oplog.rs中读取解析一个不错的选择。

oplog位于local数据下面,为了将权限最小化,大家需要创建此库的权限(还可以将权限细化到集合,再次不讨论)。

习惯性的,在local数据库下面创建,但是报错了。

执行脚本

db.createUser(
{
user: "testuser_local",
pwd: "testuser_local",
roles: [ { role: "read", db: "local" } ]
}
)

报错信息

2018-XX-XXT14:48:30.437+0800 E QUERY [thread1] Error: couldn't add user: Cannot create users in the local database :
_getErrorWithCode@src/mongo/shell/utils.js:25:13
DB.prototype.createUser@src/mongo/shell/db.js:1290:15
@(shell):1:1

查看mongoDB的官网介绍,发现确实不可以在local数据库下面创建账号

未分类

其解决方案是,我们转到admin数据库下面,创建账号。

未分类

此时可以创建成功。

注意:

(1)在程序端配置连接字符串时,相应的需要添加登入验证数据库参数 –authenticationDatabase admin

(2)通过NoSQLBooster登入时,Auth DB 选择执行创建命令的数据库名字(本实例为admin)

未分类

Default Database 的编辑项,选择oplog所在的local数据库

未分类

登入成功

(但是在测试过程中,发现此工具在这个小权限下,登入可以成功,但是有时候执行命令时报错,而通过 MongoDB shell 执行不报错。还需探究根本原因)

(3) 建议数据的拉取,在辅助节点上拉取,减少主库的压力。

记录下在linux配置mongodb+nginx+node的过程

mongodb:

1、下载mongodb3.4版本,地址: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.4.5.tgz

2、通过Xftp把文件放到已经创建好的目录里面。

3、使用tar xxx 命令,将压缩解压到当前的工作路径。顺便cd到解压后的文件夹,把所有文件的移到上一层,并删掉这个文件夹。现在目录如下

未分类

4、在bin的外层级新建一个文件夹树:data/db,以后这个db就是放数据的地方了。

5、进入到bin,使用mongod –dbpath ../data/db,就可以启动mongodb,默认端口是27017。

6、访问curl localhost:27017,看是否有这段信息,有则代表已成功启动。

未分类

7 如果bin文件夹没有index.html,可以自己建一个,开启成功后会打开这个index。

nginx:

1、先使用命令 rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 安装nginx的安装yum源。

2、安装成功后/etc/yum.repos.d会有nginx.repo文件。

3、使用yum install nginx -y,安装nginx,默认地址为/etc/nginx。我安装完后是没有html文件夹的,可以自己添加一个html文件夹,里面放一个index.html。

4、使用cd进nginx文件夹。使用nginx即可开启nginx服务。

5、如果想修改nginx.conf,修改完后使用nginx -s -reload即可重启nginx服务。

此时可以在window界面使用浏览器直接访问地址了。如,我的centos的ip是123.456.7.89,并在nginx上监听了80端口,如下:

listen       80;
server_name  localhost;
location / {
        proxy_pass  http://localhost:27017;  #被代理的服务器的域名
    proxy_redirect     off;
    proxy_set_header   Host             $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    }

6、打开浏览器,地址栏输入123.456.7.89,成功后提示

未分类

如果提示拒绝访问,可能是centos并没有开启80端口。

使用如下命令:

## 开放指定端口(永久)
firewall-cmd --zone=public --add-port=80/tcp --permanent
firewall-cmd --reload
## 查看已开放的端口
firewall-cmd --list-ports

未分类

则是再重复步骤6即可。

nodejs:

1、使用命令安装源

V8.x: 

#curl --silent --location https://rpm.nodesource.com/setup_8.x | bash -
V7.x:

#curl --silent --location https://rpm.nodesource.com/setup_7.x | bash -
V6.x:

#curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
V5.x:

#curl --silent --location https://rpm.nodesource.com/setup_5.x | bash -

2、 yum install -y nodejs

Python + MongoDB 小型程序利器

|作为一个用着和领扣 LeetCode 同样技术栈 —— Python 的程序员,对于平时一些小的想法和 Demo 自然是通过 Python 来解决,但是在学习和使用的过程中,对于数据的存储一直难以统一,最初使用纯文本文件存储,发现对于格式化索引来说纯文本存储效率太低,之后又转了 MySQL 存储,但是发现过于复杂,对于一些热更新数据来说写起来十分不雅观,限制太多,无奈便换成了 JSON 格式存储,当然,那是在现在使用的 MongoDB 之前了。

MongoDB 概要

MongoDB 用起来其实比较随意,相关命令遍写的感觉和 Python 这类弱类型语言很相似,用起来比较 Geek。

未分类

MongoDB 是一个面向文档的数据库,目前由 10gen 开发并维护,它的功能丰富,齐全,完全可以替代 MySQL。 MonogDB 的一些亮点:

  • 使用 JSON 风格 语法,易于掌握和理解:MongoDB 使用 JSON 的变种 BSON 作为内部存储的格式和语法。针对 MongoDB 的操作都使用 JSON 风格语法,客户端提交或接收的数据都使用 JSON 形式来展现。相对于 SQL来说,更加直观,容易理解和掌握。
  • Schema-less,支持嵌入子文档:MongoDB 是一个 Schema-free 的文档数据库。一个数据库可以有多个Collection,每个 Collection 是Documents的集合。Collection 和 Document 和传统数据库的 Table 和 Row并不对等。无需事先定义 Collection,随时可以创建。
  • Collection中可以包含具有不同 schema 的文档记录。 这意味着,你上一条记录中的文档有3个属性,而下一条记录的文档可以有10个属 性,属性的类型既可以是基本的数据类型(如数字、字符串、日期等),也可以是数组或者散列,甚至还可以是一个子文档(embed document)。这样,可以实现逆规范化(denormalizing)的数据模型,提高查询的速度。

未分类

如果在本地测试或者仅仅是为了临时丢一些数据进去的话,安装并启动 mongod 后直接在命令行下 mongo 即可完成连接,默认没有连接密码,如果看到类似如下提示的话,说明 MongoDB 已经安装完成了:

未分类

Python + MongoDB

下面让 Python 连接上 MongoDB:

安装 PyMongo:

pip3 install pymongo

在 Python 中引入:

import pymongo

指定数据表并连接:

# 默认的 MongoDB 监听地址
myclient = pymongo.MongoClient("mongodb://localhost:27017/")

# 使用上一步建立的 myclient 连接,并且使用 leetcode 数据库
db = myclient["leetcode"]

# 使用 db 连接的 leetcode 数据库中的 articles 表
table = db['articles']

增删改查:

# 定义我们要插入的数据,JSON 格式,在 Python 中就是 Dict 格式
post = {"author": "Nova Kwok",
        "text": "LeetCode is in China!",
        "tags": ["mongodb", "python", "pymongo"],
        "date": datetime.datetime.utcnow()}

插入一条记录:

# 插入一条记录并返回插入 ID
post_id = posts.insert_one(post).inserted_id

查询记录,这里我们需要多 import 一个包,pprint:

import pprint
pprint.pprint(posts.find_one())

返回结果:

{u'_id': ObjectId('...'),
u'author': u'Nova Kwok',
u'date': datetime.datetime(...),
u'tags': [u'mongodb', u'python', u'pymongo'],
u'text': u'LeetCode is now in China!'}

Mongodb的索引

想把Mongodb真正的使用好,不是那么简单,不能只会增删改查,还需要练习内功。
内功在武侠小说里面是一个人发展强大起来的重要基础,在我们Mongodb中练习内功也有这样的作用。
开始今天的内功学习。

为什么需要索引

索引:提高查询效率最有效的手段。是解决查询速度缓慢而退出的一种特殊的数据结构,以易于遍历的形式存储部分数据内容;索引数据存储在内存当中,同样加快了索引查找数据的效率。

从索引的简介中了解两个个知识点:

  • 目的提高查询速度。

  • 索引存储在内存当中。

索引针对的是查询速度缓慢,数据量大特别是数据量在百万级别,千万级别以及以上的数据量。
索引能大大减少查询时间的损耗。

eg:自己写过一段Monodb中的关联查询,数据表数据在百万级别,没有使用索引的时刻查询时间在7s,使用索引后查询时间是0.3s。效率大大提高。

Mongodb的索引机制

在往Mongodb中插入文档,每个文档都会经过底层的存储引擎持久化操作之后,会展示一个位置信息。
通过这个位置 信息,就能从存储引擎中读取到数据。不同的存储引擎处处位置的信息不同。选择合适的引擎也能帮助我们快速的查找数据。
eg: wiredtiger引擎生成一个KEY值,通过KEY去访问对应的文档。mmapv1引擎里面位置信息是通过文件id与文件内的偏移量决定的。

索引的类型

在Mongodb中有很多种索引支持,包含以下索引类型:单字段索引,联合索引,多key索引,文本索引, 地理位置索引,哈希索引.不同的索引类型支持不同类型的数据格式和查询需求。

单字段索引

单字段索引是针对单个字段进行设置索引的操作。

//创建索引的语法db.getCollection('test').createIndex({name:1})
{    "createdCollectionAutomatically" : false,    "numIndexesBefore" : 1,    "numIndexesAfter" : 2,    "ok" : 1.0}
数字1 是索引里面的数据按照升序进行排序,需要按照降序排序的索引可以写-1db.getCollection('test').createIndex({name:-1})

代码中针对name字段进行了创建索引,特别是Mongodb的主键_Id索引也是单字段索引。

联合索引

联合索引在单字段索引上进行了多个字段操作,将多个字段合并为一个索引的联合索引。

//创建索引的语法还是一样的。db.getCollection('test').createIndex({name:1,phone:1})
{    "createdCollectionAutomatically" : false,    "numIndexesBefore" : 2,    "numIndexesAfter" : 3,    "ok" : 1.0}

在查询字段中引入联合索引,在查询语句操作时需要按照联合索引的顺序进行查询,否则不能走索引的操作。
eg:我们创建索引时name在前 phone在后。

//find操作db.getCollection('test').find({name:"qiiq"})
db.getCollection('test').find({name:"qiiq",phone:12512135})
这两种操作是能走联合索引。//下面两种操作时不能走联合索引db.getCollection('test').find({phone:12512135,name:"qiiq"})
db.getCollection('test').find({phone:12512135})

多key索引

多key索引:当内容是数组或者list集合创建的一种索引。该索引会为数组中的每个字段创建索引。

子文档索引

该索引用来嵌入子文档中的字段进行创建索引。操作也可以有复合索引,单字段索引。

db.getCollection('test').createIndex({"user.name":1})

索引的属性

在Mongodb中不仅支持多个类型的索引,还能对索引增加一些额外的属性。

  • 唯一索引:在Mongodb中_id就是利用单字段索引加唯一索引的属性,构成的。

  • 部分索引(3.2版本之后新增):仅索引符合指定过滤器表达式集合中的文档。部分索引有较低的存储要求,降低索引的创建与维护。

  • 稀疏索引: 确保索引仅包含具有索引字段的文档的条目。会跳过没有索引字段的文档。

  • TTL索引:在一定时间后自动从集合中删除文档的一种索引。

索引的操作

索引的操作包含 创建,查看 ,删除,重建操作。

索引的创建

我们在前面的操作操作中已经使用索引的创建

db.getCollection('test').createIndex({"user.name":1})
db.collection.createIndex(keys,选项)

1、keys,要建立索引的参数列表。如:{KEY:1},其中key表示字段名,1表示升序排序,也可使用使用数字-1降序。

2、options,可选参数,表示建立索引的设置。可选值如下:

  • background,Boolean,在后台建立索引,以便建立索引时不阻止其他数据库活动。默认值 false。

  • unique,Boolean,创建唯一索引。默认值 false。

  • name,String,指定索引的名称。如果未指定,MongoDB会生成一个索引字段的名称和排序顺序串联。

  • dropDups,Boolean,创建唯一索引时,如果出现重复删除后续出现的相同索引,只保留第一个。

  • sparse,Boolean,对文档中不存在的字段数据不启用索引。默认值是 false。

  • v,index version,索引的版本号。

  • weights,document,索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。

查看索引

getIndexes()查看集合的所有索引。

db.getCollection('test').getIndexes()
[
    {        "v" : 2,        "key" : {            "_id" : 1
        },        "name" : "_id_",        "ns" : "test.test"
    },
    {        "v" : 2,        "key" : {            "name" : 1.0
        },        "name" : "name_1",        "ns" : "test.test"
    },
    {        "v" : 2,        "key" : {            "name" : 1.0,            "phone" : 1.0
        },        "name" : "name_1_phone_1",        "ns" : "test.test"
    }
]

totalIndexSize()查看集合索引的总大小。

db.getCollection('test').totalIndexSize()69632 //单位字节

索引的优化

慢查询查看

在mysql数据库中,有慢查询语句的展示,在Mongodb中也有这样的实现名字是Profiling。
更改Mongodb的阈值,有三个级别的性质。

  • 0 代表的是不开启慢分析性质。

  • 1 根据处理时间将超过阈值的请求记录都记录到system.profile集合中。

  • 2 所有记录都将记录到集合system.profile中。
    在随着业务的发展,刚开始创建的索引可能不符合现在的业务需求。索引的数量并不是越多越好。
    索引能帮助我们提高查询的性能,但是会影响到插入和更新的性能。写入与更新操作每次都需要把索引更新。
    在此就可以根据慢请求的日志,进行索引创建的调整。

索引分析

Mongodb中有一个命令explain();帮助我们进行查询的慢分析。

db.getCollection("test").find().explain()
{    "queryPlanner" : {        "plannerVersion" : 1,        "namespace" : "test.test",        "indexFilterSet" : false,        "parsedQuery" : {},        "winningPlan" : {            "stage" : "COLLSCAN",  //代表的是进行的全盘扫描,没有利用到索引。当然也是查询条件中没有指定条件语句所致
            "direction" : "forward"
        },        "rejectedPlans" : []
    },    "serverInfo" : {        "host" : "237ae74dd4d9",        "port" : 27017,        "version" : "4.0.3",        "gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
    },    "ok" : 1.0}

在name字段增加索引,执行查询计划。

db.getCollection("test").find({"name":"frq"}).explain()
{    "queryPlanner" : {        "plannerVersion" : 1,        "namespace" : "test.test",        "indexFilterSet" : false,        "parsedQuery" : {            "name" : {                "$eq" : "frq"
            }
        },        "winningPlan" : {            "stage" : "FETCH",            "inputStage" : {                "stage" : "IXSCAN",                "keyPattern" : {                    "name" : 1.0,                    "phone" : 1.0
                },                "indexName" : "name_1_phone_1",                "isMultiKey" : false,                "multiKeyPaths" : {                    "name" : [],                    "phone" : []
                },                "isUnique" : false,                "isSparse" : false,                "isPartial" : false,                "indexVersion" : 2,                "direction" : "forward",                "indexBounds" : {                    "name" : [ 
                        "["frq", "frq"]"
                    ],                    "phone" : [ 
                        "[MinKey, MaxKey]"
                    ]
                }
            }
        },        "rejectedPlans" : [ 
            {                "stage" : "FETCH",  执行完索引后,进行FETCH,读取出最终的                "inputStage" : {                    "stage" : "IXSCAN",  // 重点是这里 用到了索引字段,先在索引中查找。                    "keyPattern" : {                        "name" : 1.0
                    },                    "indexName" : "name_1",                    "isMultiKey" : false,                    "multiKeyPaths" : {                        "name" : []
                    },                    "isUnique" : false,                    "isSparse" : false,                    "isPartial" : false,                    "indexVersion" : 2,                    "direction" : "forward",                    "indexBounds" : {                        "name" : [ 
                            "["frq", "frq"]"
                        ]
                    }
                }
            }
        ]
    },    "serverInfo" : {        "host" : "237ae74dd4d9",        "port" : 27017,        "version" : "4.0.3",        "gitVersion" : "7ea530946fa7880364d88c8d8b6026bbc9ffa48c"
    },    "ok" : 1.0
}