Sep 7, 2017 - short or long field name in MongoDB

MongoDB 中使用长字段名还是短字段名的研究

起因是发现公司使用MongoDB的项目中使用的字段名都是很简短的缩写,对于我这样习惯使用长字段名的很不习惯。例如created_at, 项目中使用的crtAt。

对于长字段名,它的好处很明显:看字段名就能知道他的含义。而短字段名就不行了,必须要有外部资源来翻译。

遇到这种情况我考虑的是,之前的设计者为什么要这么做。

  • MongoDB是一个文档型数据库,每个field都会存在document中,那么会不会因为这样节省存储空间呢?这个可能性很大,但是我觉得MongoDB肯定会做压缩不会这么傻傻的。

  • 短字段名会检索更快?好像没什么道理。

针对上面两个想法使用数据生成脚本生成20w条数据,加上索引,之后测试find_one。

测试环境与脚本

MongoDB 运行在 Macbook 的docker中,使用的是默认的docker镜像,没做任何优化。

MacBook Pro(2013 Late)/2.4G i5/8GB
Docker Version 17.06.1-ce-mac24 (18950)
MongoDB server version: 3.4.7

数据生成脚本

import random
import datetime

from pymongo import MongoClient



columns = {
    "waagenophyllidae_id": {"short_name": "wid", "type": int},
    "placanticline _id": {"short_name": "pid", "type": int},
    "placanticline_station_id": {"short_name": 'psid', "type": int},
    "aliantista_id": {"short_name": 'aid', "type": int},
    "graafian_id": {"short_name": 'gid', "type": int},
    "coabitazione_id": {"short_name": 'cid', "type": int},
    "inbattibile_at": {"short_name": 'iat', "type": datetime.datetime},
    "ouagadougou_at": {"short_name": 'oat', "type": datetime.datetime},
    "demagnetise_amount": {"short_name": "da", "type": int},
    "caatinga_amount": {"short_name": "ca", "type": int},
    "status": {"short_name": "st", "type": int},
    "last_opacifiedglass_code": {"short_name": "loc", "type": int},
    "tranformational_in": {"short_name": "ti", "type": int},
    "tranformational_out": {"short_name": "to", "type": int},
    "created_at": {"short_name": "cat", "type": datetime.datetime},
    "modified_at": {"short_name": "mat", "type": datetime.datetime},
}


if __name__ == '__main__':
    client = MongoClient()
    db = client['test']
    long_field_collection = db['long_field_collection']
    short_field_collection = db['short_field_collection']


    long_field_data = {}
    short_field_data = {}

    for i in range(200000):
        if "_id" in long_field_data:
            long_field_data.pop("_id")
        if "_id" in short_field_data:
            short_field_data.pop("_id")

        if i and i % 20000 == 0:
            print("inserted {} document".format(i))

        for field, detail in columns.items():
            if detail['type'] is datetime.datetime:
                value = datetime.datetime(2017, random.randint(1,12), random.randint(1,28), random.randint(0,23), random.randint(0,59), random.randint(0,59))
            else:
                value = random.randint(1,100)
            long_field_data[field] = value
            short_field_data[detail['short_name']] = value
        long_field_collection.insert(long_field_data)
        short_field_collection.insert(short_field_data)

测试find_one 脚本

import timeit
import random
import time

from pymongo import MongoClient


mongo = MongoClient()


def repeat(collection, field, number=10000):
    s = time.time()
    for i in range(number):
        collection.find_one({field: random.randint(1,100)})
    return time.time() - s




if __name__ == '__main__':
    long_field_collection = mongo['test']['long_field_collection']
    short_field_collection = mongo['test']['short_field_collection']

    print("long field find_one test:", [repeat(long_field_collection, 'waagenophyllidae_id') for i in range(5)])
    print("short field find_one test:", [repeat(short_field_collection, 'wid') for i in range(5)])

short field name 是否更省空间

不影响 index 大小

> db.short_field_collection.totalIndexSize()
2990080
> db.long_field_collection.totalIndexSize()
2957312

影响存储空间大小

> db.short_field_collection.storageSize()
21504000
> db.long_field_collection.storageSize()
33783808

非常影响数据大小

> db.short_field_collection.dataSize()
35600000
> db.long_field_collection.dataSize()
76400000

影响 totalSize

> db.short_field_collection.totalSize()
24494080
> db.long_field_collection.totalSize()
36741120

short field name 是否影响检索(find_one)

不影响

long field find_one test: [17.11999201774597, 12.968364953994751, 11.35543704032898, 16.556156873703003, 13.168859958648682]
short field find_one test: [11.312017917633057, 12.013270854949951, 15.31855320930481, 11.15831995010376, 11.852614879608154]

结论

不推荐使用 short field name(短字段名)

  • 对存储空间有影响,在我这个几乎都是int数值的document中,长字段名多使用了 50%多的空间,但是由于这样的document占用空间很小,我觉得使用短字段名导致的可读性损失非常不划算。

  • 对index大小以及检索速度完全没有任何影响。

Aug 11, 2016 - zdic alfred workflow

源起是因为最近一段时间有点迷昆剧。与昆剧第一次接触是在上海朱家角古镇看了”实景园林昆曲《牡丹亭》“。它在园林中辟出一块山水亭台具备之地,美轮美奂,优雅非常。我独爱昆曲的优雅。我骨子里是一个附庸风雅的人,之后就爱上了。

因为看昆曲,所以就开始看汤显祖的临川四梦,所以我就遇到很多生僻字。也经常有用汉典,于是就萌发了做一个zdic alfred workflow 的想法。

项目地址:github

项目是用python开发的,用到了一个开发alfred workflow的python框架。东西很简单,就是用requests获取网页数据,然后用beatifulsoup处理html。可以在github中直接下载到export出来的workflow,下载之后即可直接是用。

目前简单实现了查字的功能,会有一些bug存在,并且由于是解析汉典的html数据,所以汉典页面有改动就会影响到功能。我会在使用过程中随时修正,当然最好是汉典能有API了。

使用方式也很简单,keyword是 “z”,之后输入想要查询的中文字即可。

z 饕

Mar 24, 2016 - torndb 使用中需要注意的几点

已经很长时间没用过torndb了。因为前两天写一个微信相关的h5小游戏,涉及到mysql存储用户的数据,也不是很复杂,所以用torndb。简单总结几个需要注意的点。

1、max_idle_time

默认max_idle_time是7*3600,因为mysql服务器的默认wait_time=28800。这个请一定根据自己的mysql服务器的配置修改。否则经常出现”MySQL server has gone aways”错误也是很糟心的。

2. charset

默认是utf8,但是现在emoji流行所以一般都修改为 utf8mb4了。当然了,请检查你的数据库支持情况。

3. autocommit

默认设置了 autocommit=1,所以如果想要使用 SELECT … FOR UPDATE 或者 SELECT … LOCK IN SHARE MODE 之类锁的话。请务必要先关闭autocommit。

try:
    conn.execute("set autocommit=0")
    # do anything you want
finally:
    # 务必在finally中把autocommit设回来。
    comm.execute("set autocommit=1")