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。也就是如下表结构
查看源代码打印帮助
1 { "_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 默认主键,mongodb自增 // id 表中自增主键值 // tablename 表名 |
获取下一个id的核心代码,继承了mongodb的ids Model。其中一个方法专门来做这个事情。
代码如下 | 复制代码 |
// db.ids.findAndModify({update:{$inc: {id:1}},query:{tablename:'topic'},new:true}); return $newid['id']; // 等同于db.ids.findAndModify({query: {tablename: 'topic'}, update: {$inc: {id: 1}}}); |
这样在获取到最新的id值后,insert event_locations 时需要将id加入到sql语句中(原先id是autoincrement,id字段没有在sql中)。之后也就是同样的业务流程了。
扩展:
有时候可能会遇到一些奇怪的需求,比如一张表里面有两个自增id要实现。。。如此,上面的表结构就不能实现了。
代码如下 | 复制代码 |
1 // _id 默认主键,mongodb自增 |
如此就行了哦
有时候也会碰到一些奇怪的需求,比如要从指定的值如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}); |