MongoEngine

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 查询混用

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的模型, 增加字段, 对于老数据并无实际影响, 因此需要评估字段不存在的业务影响.

出现影响后, 有两种方式解决这种问题.

  1. 使用脚本对老数据进行补充.

  2. 在业务代码中做兼容.

尽量使用第一种方法.

如果数据中出现了mongoengine模型中不存在的字段, 会报异常, 直接修改数据,需要注意这个问题. 可以在meta设置strictFalse来取消这个限制.

ListField 索引

加在ListField, 即array上的索引, 在引擎级别是btree索引.

使用DictField

在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是变化的.

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, 不然会把整个数据集全部取回来计数.