MongoDB 自增 id 的生成

2022-06-27 12:57:44 浏览数 (1)

1. 概述

我们使用 MySQL 等关系型数据库时,主键都是设置成自增的。 但在分布式环境下,尤其是在分库分表以后,单纯的自增主键会产生冲突,需要考虑如何生成唯一 ID。 这一点上,mongodb 预先考虑到并采取措施保证了分布式环境中生成的 id 的唯一性。 那么,mongodb 是如何做的呢?这么做有什么好处,又有什么不足呢?本文我们就来介绍一下。

2. MongoDB 中 _id 的生成

mongodb 采用了一个称之为 ObjectId 的类型来做主键,ObjectId 是一个12字节的 BSON 类型字符串,如下图所示。

前9个字节就保证了同一秒钟不同机器不同进程产生的 ObjectId 是唯一的。 而最后三个字节则在每一个进程中通过生成随机数,并以此为基础自增,确保相同进程的同一秒产生的ID也是不同的。 每个进程一秒钟可以最多拥有 256^3(16777216)个不同的 ObjectId 而不会产生冲突。

与此同时,在 _id 中已经保存了时间信息,让我们可以轻易的获取到文档首次插入的时间:

代码语言:javascript复制
> objid = new ObjectId() 
> ObjectId("53102b43bf1044ed8b0ba36b") 
> objid.getTimestamp() 
> ISODate("2014-02-28T06:22:59Z")

而另一方面,由于时间戳信息被保存在前 3 个字节中,这使得默认排序下,文档数据可以很容易按照插入顺序排序。

3. MongoDB 的哲学

这样设计的主键 ID 从根本上保证了其唯一性,也因此可以不必由 MongoDB 服务器生成,通常,主键 _id 的生成都是由客户端的驱动程序完成的。 这个做法很好的体现了MongoDB的哲学:能交给客户端驱动程序来做的事情就不要交给服务器来做。 我们知道,扩展数据服务应用层也要比扩展数据库层容易得多,这样的设计和实现,很大程度上减轻了数据库扩展的负担。

4. 缺点

虽然在同一个进程内的一秒内生成的多个主键 id 是自增的,但是在数据库全局是没有这样的规律的。 有时,能够完全自增的 id 对于应用业务来说是非常重要的。 同时,’53102b43bf1044ed8b0ba36b’ 这样的字符串对于我们来说也非常不直观,更不用说读写和记忆了。 MongoDB 允许我们自己生成 _id,但是这样唯一性的压力就又来了,在并发环境下保证自增 ID 的严格自增与避免 ID 冲突有时是需要丰富的经验的。

5. 自己生成自增 id — findAndModify

虽然已经有很多生成自增 id 的方案可供选用,如依赖 redis 等,但 MongoDB 本身提供了原子操作,我们可以通过 MongoDB 提供的原子操作来实现 id 的自增。 MongoDB 的 findAndModify 命令可以指定将获取某个键并同时进行某个操作,比如增加操作,从而实现某个字段的自增。 当然,findAndModify 命令是非常强大的,他提供了原子的增删改查操作,本文仅对其自增的方法进行讲解,其他操作较为类似。

5.1. 创建 collection

我们先创建一个自动增长 id 的集合:

代码语言:javascript复制
> db.ids.save({name:"user", id:0});
> db.ids.find();
{ "_id" : ObjectId("4c637dbd900f00000000686c"), "name" : "user", "id" : 0 }

5.2. 获取自增 id

通过下面的命令就可以获取自增 ID 了。

代码语言:javascript复制
> userid = db.ids.findAndModify({update:{$inc:{'id':1}}, query:{"name":"user"}, new:true});
{ "_id" : ObjectId("4c637dbd900f00000000686c"), "name" : "user", "id" : 1 }

6. 通过 php 生成 MongoDB 自增 id

代码语言:javascript复制
<?php
function mid($name, $db){
    $update = array('$inc'=>array("id"=>1));
    $query = array('name'=>$name);
    $command = array(
        'findandmodify'=>'ids', 'update'=>$update,
        'query'=>$query, 'new'=>true, 'upsert'=>true
    );
    $id = $db->command($command);
    return $id['value']['id'];
}

$conn = new Mongo();
$db = $conn->idtest;
$id = mid('user', $db);
$db->user->save(array('uid'=>$id, 'username'=>'techlog', 'password'=>'techlog', 'info'=>'http://techlog.cn'));
$conn->close();
?>

7. 通过 python 生成 MongoDB 自增 id

代码语言:javascript复制
import pymongo

client = pymongo.MongoClient()
db = client.techlogdb
if db.ids.find_one({'name' : 'user'}) is None:
    db.ids.save({'name' : 'user', 'id' : 0})

result = db.ids.find_and_modify(
    query={'name': 'user'},
    fields={'id': 1, '_id': 0},
    update={'$inc': {'id' : 1}},
    new=True
)
increment_id = result['id']

8. 参考资料

https://www.tutorialspoint.com/mongodb/mongodb_autoincrement_sequence.htm。 https://stackoverflow.com/questions/19524725/how-to-autoincrement-id-at-every-insert-in-pymongo。

0 人点赞