Back to home

Python中shelve的存储效率

如果某个临时需求突然需要存储服务,额外开表肯定是不太合适的.自己额外去实现一套本地文件存储又似乎很麻烦. 好在Python为我们提供了很多基于文件的存储服务.

shelve

就是这样的一个应用场景.记录下载用户的信息. Django作为应用服务程序. 在视图中处理已登录的用户并且记录其ID并记录当前下载时间.

download_mapping = {
    "1": "http://someurl.txt"
}

@need_login
def statis_download(req, download_id):
    if download_id in download_mapping:

        db = shelve.open(settings.API_DOWNLOAD_SAVE_PATH)
        db[req.session["cur_ukey"]] = time.time()
        db.close()

        return HttpResponseRedirect(download_mapping[download_id])

    return HttpResponseRedirect("/")

整段代码似乎很直接.然而发现自己乎略了很多问题.

读写效率

为了衡量shelve库提供的存储服务.所以就做出了如下实验.

import shelve

COUNT = 1000000
db = None

def insert():
    for i in xrange(COUNT):
        db[str(i)] = i

def select():
    for key in db.keys():
        db[key]

def update():
    for n, key in enumerate(db.keys()):
        db[key] = n + 1

def delete():
    for key in db.keys():
        del db[key]

if __name__ == "__main__":
    global db
    db = shelve.open("ok")

    insert()
    select()
    update()
    delete()

    db.close()

结果很不满意,起初在10000次读写的情况下,发现需要200s.恶狠狠的打开cProfile查看执行实践效率.发现一处可疑的地方.

10000 0.138 0.000 192.971 0.019 shelve.py:126(__delitem__)
10000 0.039 0.000 192.833 0.019 dumbdbm.py:186(__delitem__)

查看dumbdbm.py 源码.发现每次 delitem 多了一次 commit 调用.而调用commit时都会进行一次磁盘写入.也就难怪速度很慢了.

def _commit(self):
    if self._index is None:
        return # nothing to do

    try:
        self._os.unlink(self._bakfile)
    except self._os.error:
        pass

    try:
        self._os.rename(self._dirfile, self._bakfile)
    except self._os.error:
        pass

    f = self._open(self._dirfile, 'w', self._mode)
    for key, pos_and_siz_pair in self._index.iteritems():
        f.write("%r, %r\n" % (key, pos_and_siz_pair))
    f.close()

解决方案

不打算改写其实现,现在需求也没有维护整个记录的要求,于是乎不使用删除的shelve对象还是非常符合我的需要的.

一切设计都是基于有限的条件下.