`
xiamizy
  • 浏览: 89907 次
  • 性别: Icon_minigender_1
  • 来自: 南京
博客专栏
78437efc-ad8e-387c-847f-a092d52e81a6
spring framew...
浏览量:4887
社区版块
存档分类
最新评论

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查询语句
		Query query = new Query();
		if (id != null)
		{
			query.addCriteria(Criteria.where("_id").gt(id));
		}
		query.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的误区及引起的一系列问题

    MongoDB的ObjectId是一个12字节的BSON类型数据,用于唯一标识文档。它由四个部分组成,每个部分都有特定的用途,以确保全局唯一性。以下是对标题和描述中涉及知识点的详细解释: 1. **时间戳(Timestamp)**: ...

    MongoDB的ObjectId.pdf

    MongoDB 的 ObjectId 是一个关键的数据类型,它在数据库系统中起着至关重要的作用,特别是在文档数据库如 MongoDB 中。ObjectId 是一个 12 字节的唯一标识符,用于确保每条文档都有其独一无二的身份。在深入讨论 ...

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

    在MongoDB数据库中,ObjectId是用于唯一标识文档的关键字段,它是一个12字节的BSON类型数据。本文将深入探讨如何在C#环境中生成和处理MongoDB的ObjectId。 首先,ObjectId由四部分组成,每部分都有特定的意义: 1. ...

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

    在MongoDB中,每个文档都有一个默认的主键字段`_id`,其类型为ObjectId。ObjectId是一个12字节的二进制结构,通常用于唯一标识文档。在本文中,我们将深入探讨如何从ObjectId中提取时间信息。 ObjectId的结构如下:...

    java查询mongodb中的objectid示例

    `ObjectId`是MongoDB中用于唯一标识文档的一种数据类型,通常作为每个文档的默认 `_id` 字段。本示例将详细解释如何使用Java来查询具有指定`ObjectId`的MongoDB文档。 首先,确保你的项目中已经添加了MongoDB的Java...

    MongoDB ObjectId

    MongoDB ObjectId的设计巧妙地结合了时间戳、机器标识和进程信息,以及随机数,既确保了文档的唯一性,又简化了在分布式环境下的操作,是MongoDB数据库体系中的一个重要组成部分。在实际应用中,理解并合理利用...

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

    MongoDB中的每个文档都有一个唯一的标识符,这就是`_id`字段,它默认是`ObjectId`类型。`ObjectId`是由12字节(96位)组成的,其中前4个字节表示创建该`ObjectId`的秒数,接下来3个字节是机器标识符,接着两个字节是...

    Spring Data MongoDB中文文档

    ### Spring Data MongoDB中文文档知识点概览 ...总之,**Spring Data MongoDB** 是一个强大的工具,它不仅简化了与 MongoDB 数据库的交互,还提供了一系列实用的功能,使得开发者能够更加高效地开发应用。

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

    在Node.js环境中,当你使用MongoDB作为数据库时,经常需要处理`ObjectId`作为查询条件的情况。MongoDB在插入新文档时,会自动生成一个唯一的`ObjectId`作为文档的 `_id` 字段,这个字段通常用于唯一标识每条记录。...

    spring mongodb 中文文档

    Spring Data MongoDB 提供了一组高级抽象,用于在Java 应用程序中操作MongoDB 数据。它包括Repository 模式,使得与MongoDB 的交互变得简单且类型安全。 3. **配置MongoDB** 在Spring Boot 应用中,可以通过...

    mongoDB的官方中文文档

    MongoDB是一种流行的开源、分布式文档型数据库,以其灵活性、高性能和易用性而备受开发者青睐。作为NoSQL数据库的一种,它存储数据的方式不同于传统的表结构,而是采用键值对、文档、集合的形式。MongoDB的官方中文...

    MongoDB插入文档与ObjectID操作.pdf

    5. **ObjectId 数据类型**:ObjectId 是 MongoDB 中的一种特殊数据类型,它是一个12字节的BSON类型数据,通常用于 `_id` 字段。 6. **ObjectId 结构**:ObjectId 的前4个字节代表创建该 ObjectId 的时间戳,精确到...

    MongoDB参考手册.zip_MongoDB_T6U_mongodb中文手册

    MongoDB是一款分布式文档型数据库,以其灵活性、高性能和易扩展性在现代Web开发中占据了重要地位。MongoDB中文手册是开发者必备的参考资料,它详细介绍了MongoDB的各种操作和语法用法,帮助用户深入理解并有效利用这...

    MongoDB 3.6 中文文档

    MongoDB 3.6 中文文档

    NoSQLBooster for MongoDB

    NoSQLBooster提供了一系列功能,旨在提升MongoDB的使用体验: 1. **图形化用户界面(GUI)**:NoSQLBooster提供了一个直观的GUI,允许用户通过简单的拖放操作和清晰的布局来执行查询、插入、更新和删除操作,极大地...

    Mongodb + GridFS +Java 操作Mongodb中存储的文件

    MongoDB 是一个流行的开源、高性能、无模式的文档型数据库,特别适合处理大规模的数据。GridFS 是 MongoDB 提供的一种用于存储和检索大型文件的标准规范,它将大文件分割成多个小块(chunks)存储,方便高效管理和...

Global site tag (gtag.js) - Google Analytics