# MongoEngine mongoengine 是基于 pymongo 封装的 mongodb orm. 使用有一些有点: - 更直观的 api 封装,不用使用 mongo 蹩脚的 dsl 操作数据库. - 存在数据 model, 克服 schema-free 带来的一些问题. - 使用方便 ## 多进程服务器中连接的注意项 `uwsgi`使用 prefork 模型时, 要注意下防止 socket fd 被共享带来的问题. 初始化连接时不要即时连接,而是等到实际使用时再去第一次建立连接. ```python connect( alias=conf.social_mongodb_alias, host=conf.social_mongodb_host, replicaSet=conf.social_mongodb_replicaset, connect=False, read_preference=ReadPreference.SECONDARY_PREFERRED ) ``` 注意`connect=False`. ## 注意分析业务场景对一致性的要求 `mongodb`本身不支持跨文档的事务, 因此本身对强一致性有要求的跨文档业务不应该选择使用它. `wiredtiger`引擎本身支持文档级别的锁, 但是在 `mongodb` 中, 我们不能显式的利用它, 因此需要在一些场景中找可用的原子操作解决问题. ## 更新或创建 如果存在指定文档,更新之, 否则, 创建新文档. ``` ModelA.object(query..).update(updates.., upsert=True) ``` 需要注意, 使用 upsert 时, 如果创建文档, 模型中定义的 default 值,不会在新建文档中体现, 因此 update 的字段需要满足 required 字段都有更新值. ## 原生查询与 mongoengine 查询混用 mongoengine 有一些原生查询特性不支持, 在应用中可能出现混用的情况, 使用`__raw__={}`来描述原生查询. 一个实际的混用案例. ```python data = dict( __raw__={ '$addToSet': { 'items': { '$each': item_ids } } }, inc__item_num=len(item_ids) ) n = MvPlaylist.objects(pk=playlist_id).update_one(**data) ``` 使用`addToSet`这个 mongodb 操作, 同时用 mongoengine 的方式, 对 item_num 这个字段增加了`len(item_ids)`个数量. ## 模型增加字段 `mongoengine`的模型, 增加字段, 对于老数据并无实际影响, 因此需要评估字段不存在的业务影响. 出现影响后, 有两种方式解决这种问题. 1. 使用脚本对老数据进行补充. 2. 在业务代码中做兼容. 尽量使用第一种方法. 如果数据中出现了`mongoengine`模型中不存在的字段, 会报异常, 直接修改数据,需要注意这个问题. 可以在`meta`设置`strict`为`False`来取消这个限制. ## ListField 索引 加在`ListField`, 即`array`上的索引, 在引擎级别是`btree`索引. ## 使用DictField 在key不确定的场景中使用. 一个实际案例, 投票系统, 每一个投票项目的选项都不一致, 怎么维护每个选项的计数器. 定义一个model. ``` class Vote(Document): meta = { 'db_alias': 'social', 'indexes': ['name', ] } name = StringField(default='') # options = DictField() ``` 每次投票的时候, 对指定的option计数器加1. ``` Vote.objects(name=name).update_one(__raw__={ '$inc': { 'options.{}'.format(option): 1 } }, upsert=True) ``` 使用`__raw__`, 而不是直接`inc__options__option`的原因是, option是变化的. ## Mongo 中判断一个字段类型是array ``` rs = db.comment.find({'sns_likes': {'$gte': []}}) ``` 使用`$type`没有成功, 不知道什么原因. ## 只在创建时修改某字段 ``` n = UserFavorRecords.objects(user_id=pk).update( __raw__={ '$push': { 'records': { '$each': [ {'aid': aid, 'ftime': datetime.now()} ] } }, '$setOnInsert': { 'has_merged_device': False } }, user_id=pk, user_type=0, mtime=datetime.now(), upsert=True ) ``` 使用`setOnInsert`函数达到这个目的. ## 计数 获取`array`字段的长度, 用`count`方法, 不要使用`len`, 不然会把整个数据集全部取回来计数.