MongoDB中ObjectId的误区及引起的一系列问题

近期对两个应用进行改造,在上线过程中出现一系列问题(其中一部分是由于ObjectId误区导致的)

先来了解下ObjectId:

TimeStamp

前 4位是一个unix的时间戳,是一个int类别,我们将上面的例子中的objectid的前4位进行提取“4df2dcec”,然后再将他们安装十六进制 专为十进制:“1307761900”,这个数字就是一个时间戳,为了让效果更佳明显,我们将这个时间戳转换成我们习惯的时间格式(精确到秒)

$ date -d '1970-01-01 UTC 1307761900 sec' -u

2011年 06月 11日 星期六 03:11:40 UTC

前 4个字节其实隐藏了文档创建的时间,并且时间戳处在于字符的最前面,这就意味着ObjectId大致会按照插入进行排序,这对于某些方面起到很大作用,如 作为索引提高搜索效率等等。使用时间戳还有一个好处是,某些客户端驱动可以通过ObjectId解析出该记录是何时插入的,这也解答了我们平时快速连续创 建多个Objectid时,会发现前几位数字很少发现变化的现实,因为使用的是当前时间,很多用户担心要对服务器进行时间同步,其实这个时间戳的真实值并 不重要,只要其总不停增加就好。

Machine

接下来的三个字节,就是 2cdcd2 ,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectid中间的字符串都是一模一样的原因。

pid

上面的Machine是为了确保在不同机器产生的objectid不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectid不冲突,接下来的0936两位就是产生objectid的进程标识符。

increment

前面的九个字节是保证了一秒内不同机器不同进程生成objectid不冲突,这后面的三个字节a8b817,是一个自动增加的计数器,用来确保在同一秒内产生的objectid也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。

ObjectId唯一性

大家可能会觉得,在某种程度上已经可以保证唯一了,不管在客户端还是在服务端。

误区 一 、文档顺序和插入顺序一致?

单线程情况

ObjectId中的timestamp、machine、pid、inc都可以保证唯一,因为在同一台机器,同一个进程。

这里有一个问题,mongodb的操作时多线程的。a、b、c...几个线程进行入库操作时,不能保证哪一条可以在另外一条之前,所以会是乱序的。

多线程、多机器或多进程情况

再看下ObjectId中mache、pid不能保证唯一。那么则数据更加会是乱序的。

解决办法:

由于collection集合中数据是无序的(包括capped collection),那么,最简单的办法是对ObjectId进行排序。

可以使用两种方法排序,

1.mongoDB查询语句

jQuery query = new Query();
if (id != null)
{
  jquery.addCriteria(Criteria.where("_id").gt(id));
}
jquery.with(new Sort(Sort.Direction.ASC, "_id")); 

2.java.util.PriorityQueue

Comparator<DBObject> comparator = new Comparator<DBObject>()
{
  @Override
  public int compare(DBObject o1, DBObject o2)
  {
    return ((ObjectId)o1.get("_id")).compareTo((ObjectId)o2.get("_id"));
  }
};
PriorityQueue<DBObject> queue = new PriorityQueue<DBObject>(200,comparator); 

误区 二 、多客户端高并发时,是否可以保证顺序(sort之后)?

如果一直保证写入远远大于读出(间隔一秒以上),这样是永远不会出现乱序的情况。

我们来看下样例

现在看到图中,取出数据两次

第一次

4df2dcec aaaa  ffff 36a8b813
4df2dcec aaaa  eeee 36a8b813
4df2dcec bbbb  1111 36a8b814

第二次

4df2dcec bbbb  1111 36a8b813
4df2dcec aaaa  ffff 36a8b814
4df2dcec aaaa  eeee 36a8b814

现在如果取第一次的最大值(4df2dcec bbbb  1111 36a8b814)做下次查询的结果,那么就会漏掉

第二次的三条,因为(4df2dcec bbbb  1111 36a8b814)大于第二次取的所有记录。

所以会导致丢数据的情况。

解决办法:

由于ObjectId的时间戳截止到秒,而counter算子前四位又为机器与进程号。

1.处理一定时间间隔前的记录(一秒以上),这样即使机器和进程号导致乱序,间隔前也不会出现乱序情况。

2.单点插入,原来分布到几个点的插入操作,现在统一由一个点查询,保证机器与进程号相同,使用counter算子使记录有序。

这里,我们用到了第一种办法。

误区 三 、不在DBObject设置_id使用mongoDB设置ObjectId?

mongoDB插入操作时,new DBBasicObject()时,大家看到_id是没有被填值的,除非手工的设置_id。那么是否是服务端设置的呢?

大家来看下插入操作的代码:

实现类

public WriteResult insert(List<DBObject> list, com.mongodb.WriteConcern concern, DBEncoder encoder ){
     if (concern == null) {
       throw new IllegalArgumentException("Write concern can not be null");
     }
     return insert(list, true, concern, encoder);
   } 

可以看到需要添加,默认都为添加

protected WriteResult insert(List<DBObject> list, boolean shouldApply , com.mongodb.WriteConcern concern, DBEncoder encoder ){
      if (encoder == null)
        encoder = DefaultDBEncoder.FACTORY.create();
      if ( willTrace() ) {
        for (DBObject o : list) {
          trace( "save: " + _fullNameSpace + " " + JSON.serialize( o ) );
        }
      }
      if ( shouldApply ){
        for (DBObject o : list) {
          apply(o);
          _checkObject(o, false, false);
          Object id = o.get("_id");
          if (id instanceof ObjectId) {
            ((ObjectId) id).notNew();
          }
        }
      }
      WriteResult last = null;
      int cur = 0;
      int maxsize = _mongo.getMaxBsonObjectSize();
      while ( cur < list.size() ) {
        OutMessage om = OutMessage.insert( this , encoder, concern );
        for ( ; cur < list.size(); cur++ ){
          DBObject o = list.get(cur);
          om.putObject( o );
          // limit for batch insert is 4 x maxbson on server, use 2 x to be safe
          if ( om.size() > 2 * maxsize ){
            cur++;
            break;
          }
        }
        last = _connector.say( _db , om , concern );
      }
      return last;
    } 

自动添加ObjectId的操作

/**
  * calls {@link DBCollection#apply(com.mongodb.DBObject, boolean)} with ensureID=true
  * @param o <code>DBObject</code> to which to add fields
  * @return the modified parameter object
  */
 public Object apply( DBObject o ){
   return apply( o , true );
 }
 /**
  * calls {@link DBCollection#doapply(com.mongodb.DBObject)}, optionally adding an automatic _id field
  * @param jo object to add fields to
  * @param ensureID whether to add an <code>_id</code> field
  * @return the modified object <code>o</code>
  */
 public Object apply( DBObject jo , boolean ensureID ){
   Object id = jo.get( "_id" );
   if ( ensureID && id == null ){
     id = ObjectId.get();
     jo.put( "_id" , id );
   }
   doapply( jo );
   return id;
 } 

可以看到,mongoDB的驱动包中是会自动添加ObjectId的。

save的方法

public WriteResult save( DBObject jo, WriteConcern concern ){
    if ( checkReadOnly( true ) )
      return null;
    _checkObject( jo , false , false );
    Object id = jo.get( "_id" );
    if ( id == null || ( id instanceof ObjectId && ((ObjectId)id).isNew() ) ){
      if ( id != null && id instanceof ObjectId )
        ((ObjectId)id).notNew();
      if ( concern == null )
        return insert( jo );
      else
        return insert( jo, concern );
    }
    DBObject q = new BasicDBObject();
    q.put( "_id" , id );
    if ( concern == null )
      return update( q , jo , true , false );
    else
      return update( q , jo , true , false , concern );
  } 

综上所述,默认情况下ObjectId是由客户端生成的,并不是不设置就由服务端生成的。

误区 四 、findAndModify是否真的可以获取到自增变量?

DBObject update = new BasicDBObject("$inc", new BasicDBObject("counter", 1));
    DBObject query = new BasicDBObject("_id", key);
    DBObject result = getMongoTemplate().getCollection(collectionName).findAndModify(query, update);
    if (result == null)
    {
      DBObject doc = new BasicDBObject();
      doc.put("counter", 1L);
      doc.put("_id", key);
      // insert(collectionName, doc);
      getMongoTemplate().save(doc, collectionName);
      return 1L;
    }
    return (Long) result.get("counter"); 

获取自增变量会使用这种方法编写,但是,我们执行完成后会发现。

findAndModify操作,是先执行了find,再执行了modify,所以当result为null时,应该新增并返回0

以上所述是小编给大家介绍的MongoDB中ObjectId的误区及引起的一系列问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 关于C#生成MongoDB中ObjectId的实现方法

    ObjectId介绍在MongoDB中,文档(document)在集合(collection)中的存储需要一个唯一的_id字段作为主键.这个_id默认使用ObjectId来定义,因为ObjectId定义的足够短小,并尽最大可能的保持唯一性,同时能被快速的生成. ObjectId 是一个 12 Bytes 的 BSON 类型,其包含:1.4 Bytes 自纪元时间开始的秒数2.3 Bytes 机器描述符3.2 Bytes 进程ID4.3 Bytes 随机数 从定义可以看出,在同一秒内,在不同的机器

  • python根据时间生成mongodb的ObjectId的方法

    本文实例讲述了python根据时间生成mongodb的ObjectId的方法.分享给大家供大家参考.具体分析如下: mongodb的_id为ObjectId类型,ObjectId内是包含时间戳信息的,这样我们在保存数据的时候就不需要再单独记录一个添加时间了,如果需要按照时间查询,我们可以先把时间变化成可查询的ObjectId,再通过_id字段查询,由于mongodb的_id是主键,查询效率非常高.下面的函数给出了如何把时间换算成ObjectId,同时该函数还可以指定时间的偏移量,比如多少天前的时

  • python将MongoDB里的ObjectId转换为时间戳的方法

    本文实例讲述了python将MongoDB里的ObjectId转换为时间戳的方法.分享给大家供大家参考.具体分析如下: MongoDB里的_id字段前四位是时间戳的16进制表示,通过Python可以很容易从_id中提取出时间戳来 def timestamp_from_objectid(objectid): result = 0 try: result = time.mktime(objectid.generation_time.timetuple()) except: pass return r

  • 深究从MongoDB的ObjectId中获取时间信息

    MongoDB默认使用_id字段作为主键,类型为ObjectId.ObjectId的生成有一定的规则,详情可以查看这篇文章 - MongoDB深究之ObjectId.如果你在写入数据库的时候忘记写入创建时间,不用担心,完全可以通过_id字段的值来还原当时的时间.看下面的mongodb script脚本: db.getCollection('fees').find({}).forEach(function(item){ var _str = item._id.toString().substr(1

  • java查询mongodb中的objectid示例

    找了很久查询objectid的方法都是错的,用mongovue能查询出来,但就是用java不知道怎么查询 1.mongovue里的查询方式: 复制代码 代码如下: {"_id" : ObjectId("5326bfc0e6f780b21635248f")} 2.纯mongodb里的查询方式: 复制代码 代码如下: db.collect.find({ "_id" : ObjectId("5326bfc0e6f780b21635248f&q

  • MongoDB中ObjectId的误区及引起的一系列问题

    近期对两个应用进行改造,在上线过程中出现一系列问题(其中一部分是由于ObjectId误区导致的) 先来了解下ObjectId: TimeStamp 前 4位是一个unix的时间戳,是一个int类别,我们将上面的例子中的objectid的前4位进行提取"4df2dcec",然后再将他们安装十六进制 专为十进制:"1307761900",这个数字就是一个时间戳,为了让效果更佳明显,我们将这个时间戳转换成我们习惯的时间格式(精确到秒) $ date -d '1970-01

  • PHP库 查询Mongodb中的文档ID的方法

    在IBM我的一份新工作是一名开发的后勤人员.那意味着我的大部分时间是在和数据库打交道.在我的工作流程中,我花了一些时间在MongoDB上面--这是一个文档数据库.但是在通过ID来检索记录这个操作上面我碰到了一些问题.下面的代码是最终版本,以后碰到类似的问题我可以直接引用它.如果大家也需要,希望下面对大家有所帮助. MongoDB 和 IDs 当我向一个集合中插入数据的时候,我并没有设置_id字段:如果这个字段是空的话,那么MongoDB将要自动生成一个ID来使用,这对我来说是非常不错的.然而,当

  • MongoDB中对文档的增删查改基本操作方法总结

    插入文档:insert() 方法 要插入数据到 MongoDB 集合,需要使用 MongoDB 的  insert() 或 save() 方法. 语法: insert() 命令的基本语法如下: >db.COLLECTION_NAME.insert(document) 例子:  >db.mycol.insert({    _id: ObjectId(7df78ad8902c),    title: 'MongoDB Overview',     description: 'MongoDB is

  • 使用aggregate在MongoDB中查询重复数据记录的方法

    MongoDB中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果.有点类似sql语句中的 count(*). aggregate() 方法 MongoDB中聚合的方法使用aggregate(). 语法 aggregate() 方法的基本语法格式如下所示: >db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION) 我们知道,MongoDB属于文档型数据库,其存储的文档类型都是JSON对象.正是由于这一特性,我们

  • MongoDB中强大的统计框架Aggregation使用实例解析

    听说项目里面Aggregation用的多,那就专门针对这个多多练习一下. 基本的操作包括: •$project - 可以从子文档中提取字段,可以重命名字段 •$match - 可以实现查找的功能 •$limit - 接受一个数字n,返回结果集中的前n个文档. •$skip - 接受一个数字n,丢弃结果集中的前n个文档.效率比较低,依然会遍历前n个文档. •$unwind - 可以将一个包含数组的文档切分成多个, 比如你的文档有 中有个数组字段 A, A中有10个元素, 那么经过 $unwind处

  • pymongo实现控制mongodb中数字字段做加法的方法

    本文实例讲述了pymongo实现控制mongodb中数字字段做加法的方法.分享给大家供大家参考.具体分析如下: 这个非常实用,比如我们需要给文章做访问统计,可以设置一个数字字段:hit,然后每次点击后给改字段增加1即可 import pymongo #导入pymongo 模块 conn = pymongo.Connection() #连接本地mongodb数据库 db=conn.database #database为数据库名称 collection = db.article #article为c

  • mongoDB中聚合函数java处理示例详解

    1.问题 最近在做项目的时候碰到一个对mongoDB的数据处理,从MongoDB中拿到内嵌文档的时间排序的list. 一开始考虑到直接对mongoDB中的属性排序,后面发现属性存在内嵌文档中,所以处理中需要用到聚合函数. 思考 (key)解决这个问题的过程让我学到很多,发现自己在解决一个问题不仅查找问题的姿势不对,浪费太多时间.而且在碰到问题之后,应该多看看解决办法,甚至解决了之后要去思考问题,回顾问题.而不是像以前一样,解决问题了就万事大吉,抛之脑后. 2.解决 需要对document中的一个

  • Node.js使用MongoDB的ObjectId作为查询条件的方法

    当往MongoDB中插入一条数据时,会自动生成ObjectId作为数据的主键. 那么如何通过ObjectId来做数据的唯一查询呢? 在MongoDB中插入一条数据 在MongoDB中插入一条如下结构的数据: { _id: 5d6a32389c825e24106624e4, title: 'GitHub 上有什么好玩的项目', content: '上个月有水友私信问我,GitHub 上有没有比较好玩的项目可以推荐?我跟他说:"有,过两天我整理一下".\n' + '\n' + '然而,一个

随机推荐