mongoengine 是基于 pymongo 封装的 mongodb orm.
使用有一些有点:
更直观的 api 封装,不用使用 mongo 蹩脚的 dsl 操作数据库.
存在数据 model, 克服 schema-free 带来的一些问题.
使用方便
uwsgi
使用 prefork 模型时, 要注意下防止 socket fd 被共享带来的问题. 初始化连接时不要即时连接,而是等到实际使用时再去第一次建立连接.
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 有一些原生查询特性不支持, 在应用中可能出现混用的情况, 使用__raw__={}
来描述原生查询.
一个实际的混用案例.
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
的模型, 增加字段, 对于老数据并无实际影响, 因此需要评估字段不存在的业务影响.
出现影响后, 有两种方式解决这种问题.
使用脚本对老数据进行补充.
在业务代码中做兼容.
尽量使用第一种方法.
如果数据中出现了mongoengine
模型中不存在的字段, 会报异常, 直接修改数据,需要注意这个问题.
可以在meta
设置strict
为False
来取消这个限制.
加在ListField
, 即array
上的索引, 在引擎级别是btree
索引.
在key不确定的场景中使用. 一个实际案例, 投票系统, 每一个投票项目的选项都不一致, 怎么维护每个选项的计数器.
定义一个model.
class Vote(Document):
meta = {
'db_alias': 'social',
'indexes': ['name', ]
}
name = StringField(default='')
# <option_key, count>
options = DictField()
每次投票的时候, 对指定的option计数器加1.
Vote.objects(name=name).update_one(__raw__={
'$inc': {
'options.{}'.format(option): 1
}
}, upsert=True)
使用__raw__
, 而不是直接inc__options__option
的原因是, option是变化的.
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
, 不然会把整个数据集全部取回来计数.