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