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。