MongoDB实现mysql主键autoincrement

MongoDB新建表默认有一个_id字段来作为autoincrement自增实现,而这个_id字段类型是objectid类型(objectid 是12字节的BSON类型)。ObjectId的详细解释。

而现在因为数据库迁移,将项目中原mysql的一些locations表移植到mongodb上面。对于locations 使用mongodb 2d loc 索引能够更快的进行数据检索,同时避免原mysql需要经过复杂的经纬度查询。

那么数据迁移后主要的要求有

1、数据库表的迁移不会影响表与表之间的关系,继续保持表之间的主外键关系。

2、lat 与 lng 字段在mongodb 新表中合并为一个字段loc,同时对loc加以2d索引。

对于第二点主要影响是在代码之中,可以看看这里。就不做详细描述了。

那么现在主要要做的事情就是,维持表与表的关系与主外键关系。我们以event 与 对应的 event_locations为例

event中有一个外键location_id,location_id就是event_locations的主键id;而现在mongodb中新建event_locations表,同时要保持event_locations与event的主外键引用关系。

因为mongodb表中默认有着_id这样一个主键,那现在我们的数据迁移以及mongodb event_locations表结构构建有两种选择

1、event_locations 主键以_id,即将原mysql event_locations 的id 替换为 _id。也就是如下表结构


{ "_id" : ObjectId("51243bdb383ca83413000000"),  "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点就是 使用默认的_id主键值,速度快检索方便。_id自增不必维护,有mongodb自行完成。

缺点很明显 event中的location_id外键,原来类型是int,现在改用_id objectid类型作为主键id,location_id类型必须要更改为char(32)了。且程序代码还需要一定的更改。

最大的缺点就是目前项目已经上线,直接更改数据库表可能会带来不可预知的问题;对于运营维护可能带来很大压力。且对于目前event已有的location_id数据需要进行同步更新。

2、保持原来的event_locations表结构,使用id继续维持与event location_id的主外键关系。


{ "_id" : ObjectId("51243bdb383ca83413000000"), "id" : 2354, "topic_id" : 103874, "name" : "xixi aaahah", "address" : "xxxbbbb cccccc", "loc" : [ 30.11, 112.02 ], "created" : 1361329114, "updated" : 0 }

优缺点分析

优点 不必去更改event location_id的数据类型,且location_id的数据不必修改;只用将event_locations数据完整导入到新表中即可。

缺点 要维持id的自增属性,而mongodb默认是_id才有自增属性的,所以我们要用某种方法实现id的自增实现。_id会占用存储空间(默认的_id原本可以删除的,但考虑到以后可能会用到就保存了)。

我现在采用的是第2种方式,主要原因如下

1、项目已上线,且已经有了用户数据;对于直接更改数据表与操作数据会带来很大的风险。强烈不推荐。

2、event_locations数据几乎不用同步,只需要单向导入到mongodb 新表就好。

3、程序代码变动最小,目前程序代码更改就是新的loc(lng+lat)存储与id的自增实现。而第一种除了这些,还需要把所有的id替换为_id,数据类型也要从int变为string。

在确定了解决方法之后那么现在主要的难题就是怎么实现id的自增?

目前也有两种方法

1、传统实现,每次在event_locations insert之前,全表id desc排序得到最大的id值,对id值+1既得到了最新的id值从而实现了自增。

2、使用mongodb的findAndModify()方法实现,独立一个ids表用来记录所有可能需要id自增的值。

同样简要说明下这两种实现id自增的方法优劣。

第一种方法

优点 不用构建ids表(工具表),免于维护

缺点 每次都要进行全表索引排序且查找到最大id那条记录,在进行+1

第二种

优点 findandmodify() 原子操作(了解原子操作请查看这里),查找并修改,直接返回当前操作的这个文档内容。

缺点 要构建维护ids表,即项目中所有表可能需要自增的表都有一条记录存在于其中。

无论采用哪种方法都要修改程序代码,即id autoincrement部分需要程序实现。我采用的是第二种方法。

主要考虑还是性能问题,避免每次insert之前都要对event_locations进行全部的索引排序。而维护ids这样的表会很简单且性能上忽略不计,详细表结构如下


{ "_id" : ObjectId("5147ca1a08c2c1bc12abdb4c"), "id" : 2356, "tablename" : "topic" }

// _id 默认主键,mongodb自增

// id  表中自增主键值

// tablename 表名

获取下一个id的核心代码,继承了mongodb的ids Model。其中一个方法专门来做这个事情。


/**
* 构建ids用于存储所有表中需要自增的id值,结合findandmodify()来返回对应表最新的id
* @param string $tablename 表名
* @return int
*/
public function get_next_id($tablename = 'topic'){

// db.ids.findAndModify({update:{$inc: {id:1}},query:{tablename:'topic'},new:true});
$data = array(
'findAndModify' => 'ids',
'update' => array( '$inc' => array( 'id' => 1)),
'query' => array('tablename' => $tablename),
'new' => true
);
$idrecord = $this->db()->command( $data );
$newid = $idrecord['value'];

return $newid['id'];
}

// 等同于db.ids.findAndModify({query: {tablename: 'topic'}, update: {$inc: {id: 1}}});

这样在获取到最新的id值后,insert event_locations 时需要将id加入到sql语句中(原先id是autoincrement,id字段没有在sql中)。之后也就是同样的业务流程了。

扩展:

有时候可能会遇到一些奇怪的需求,比如一张表里面有两个自增id要实现。。。如此,上面的表结构就不能实现了。

<pre>// _id 默认主键,mongodb自增
// fieldname  表中自增字段名
// fieldval 表中自增id值
// tablename 表名</pre>

如此就行了哦

有时候也会碰到一些奇怪的需求,比如要从指定的值如200开始id自增。

那么其实用这样一句就可以了

db.ids.findAndModify({query: {tablename: 'topic'}, update: {id: 200,tablename: 'topic'}});
代码实现上就是注意要update时要把tablename也要传递进去了,因为update会更新所以的记录,如果不写上tablename那么返回的文档就只有一个id字段了。。。有需要的自己扩展下吧

需要注意的是event_locations id现在不是主键也没有任何索引,除非你加上了;所以这里还是强烈推荐加上索引

db.event_locations.ensureIndex({id: 1});

参考

在MongoDB中模拟Auto Increment

Create an Auto-Incrementing Sequence Field

findandmodify命令

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*