Python内存数据库/引擎
1 初探
在平时的开发工作中,我们可能会有这样的需求:我们希望有一个内存数据库或者数据引擎,用比较Pythonic的方式进行数据库的操作(比如说插入和查询)。
举个具体的例子,分别向数据库db中插入两条数据,"a=1, b=1" 和 "a=1, b=2", 然后想查询a=1的数据可能会使用这样的语句db.query(a=1),结果就是返回前面插入的两条数据; 如果想查询a=1, b=2的数据,就使用这样的语句db.query(a=1, b=2),结果就返回前面的第二条数据。
那么是否拥有实现上述需求的现成的第三方库呢?几经查找,发现PyDbLite能够满足这样的需求。其实,PyDbLite和Python自带的SQLite均支持内存数据库模式,只是前者是Pythonic的用法,而后者则是典型的SQL用法。
他们具体的用法是这样的:
PyDbLite
- import pydblite
- # 使用内存数据库
- pydb = pydblite.Base(':memory:')
- # 创建a,b,c三个字段
- pydb.create('a', 'b', 'c')
- # 为字段a,b创建索引
- pydb.create_index('a', 'b')
- # 插入一条数据
- pydb.insert(a=-1, b=0, c=1)
- # 查询符合特定要求的数据
- results = pydb(a=-1, b=0)
SQLite
- import sqlite3
- # 使用内存数据库
- con = sqlite3.connect(':memory:')
- # 创建a,b,c三个字段
- cur = con.cursor()
- cur.execute('create table test (a char(256), b char(256), c char(256));')
- # 为字段a,b创建索引
- cur.execute('create index a_index on test(a)')
- cur.execute('create index b_index on test(b)')
- # 插入一条数据
- cur.execute('insert into test values(?, ?, ?)', (-1,0,1))
- # 查询符合特定要求的数据
- cur.execute('select * from test where a=? and b=?',(-1, 0))
2 pydblite和sqlite的性能
毫无疑问,pydblite的使用方式非常地Pythonic,但是它的效率如何呢?由于我们主要关心的是数据插入和查询速度,所以不妨仅对这两项做一个对比。写一个简单的测试脚本:
- import time
- count = 100000
- def timeit(func):
- def wrapper(*args, **kws):
- t = time.time()
- func(*args)
- print time.time() - t, kws['des']
- return wrapper
- @timeit
- def test_insert(mdb, des=''):
- for i in xrange(count):
- mdb.insert(a=i-1, b=i, c=i+1)
- @timeit
- def test_query_object(mdb, des=''):
- for i in xrange(count):
- c = mdb(a=i-1, b=i)
- @timeit
- def test_sqlite_insert(cur, des=''):
- for i in xrange(count):
- cur.execute('insert into test values(?, ?, ?)', (i-1, i, i+1))
- @timeit
- def test_sqlite_query(cur, des=''):
- for i in xrange(count):
- cur.execute('select * from test where a=? and b=?', (i-1, i))
- print '-------pydblite--------'
- import pydblite
- pydb = pydblite.Base(':memory:')
- pydb.create('a', 'b', 'c')
- pydb.create_index('a', 'b')
- test_insert(pydb, des='insert')
- test_query_object(pydb, des='query, object call')
- print '-------sqlite3--------'
- import sqlite3
- con = sqlite3.connect(':memory:')
- cur = con.cursor()
- cur.execute('create table test (a char(256), b char(256), c char(256));')
- cur.execute('create index a_index on test(a)')
- cur.execute('create index b_index on test(b)')
- test_sqlite_insert(cur, des='insert')
- test_sqlite_query(cur, des='query')
在创建索引的情况下,10w次的插入和查询的时间如下:
- -------pydblite--------
1.14199995995 insert
0.308000087738 query, object call
-------sqlite3--------
0.411999940872 insert
0.30999994278 query
在未创建索引的情况(把创建索引的测试语句注释掉)下,1w次的插入和查询时间如下:
- -------pydblite--------
- 0.0989999771118 insert
- 5.15300011635 query, object call
- -------sqlite3--------
- 0.0169999599457 insert
- 7.43400001526 query
我们不难得出如下结论:
sqlite的插入速度是pydblite的3-5倍;而在建立索引的情况下,sqlite的查询速度和pydblite相当;在未建立索引的情况下,sqlite的查询速度比pydblite慢1.5倍左右。
3 优化
我们的目标非常明确,使用Pythonic的内存数据库,提高插入和查询效率,而不考虑持久化。那么能否既拥有pydblite的pythonic的使用方式,又同时具备pydblite和sqlite中插入和查询速度快的那一方的速度?针对我们的目标,看看能否对pydblite做一些优化。
阅读pydblite的源码,首先映入眼帘的是对python2和3做了一个简单的区分。给外部调用的Base基于_BasePy2或者_BasePy3,它们仅仅是在__iter__上有细微差异,最终调用的是_Base这个类。
- class _BasePy2(_Base):
- def __iter__(self):
- """Iteration on the records"""
- return iter(self.records.itervalues())
- class _BasePy3(_Base):
- def __iter__(self):
- """Iteration on the records"""
- return iter(self.records.values())
- if sys.version_info[0] == 2:
- Base = _BasePy2
- else:
- Base = _BasePy3
然后看下_Base的构造函数,做了简单的初始化文件的操作,由于我们就是使用内存数据库,所以文件相关的内容完全可以抛弃。
- class _Base(object):
- def __init__(self, path, protocol=pickle.HIGHEST_PROTOCOL, save_to_file=True,
- sqlite_compat=False):
- """protocol as defined in pickle / pickle.
- Defaults to the highest protocol available.
- For maximum compatibility use protocol = 0
- """
- self.path = path
- """The path of the database in the file system"""
- self.name = os.path.splitext(os.path.basename(path))[0]
- """The basename of the path, stripped of its extension"""
- self.protocol = protocol
- self.mode = None
- if path == ":memory:":
- save_to_file = False
- self.save_to_file = save_to_file
- self.sqlite_compat = sqlite_compat
- self.fields = []
- """The list of the fields (does not include the internal
- fields __id__ and __version__)"""
- # if base exists, get field names
- if save_to_file and self.exists():
- if protocol == 0:
- _in = open(self.path) # don't specify binary mode !
- else:
- _in = open(self.path, 'rb')
- self.fields = pickle.load(_in)
紧接着比较重要的是create(创建字段)、create_index(创建索引)两个函数:
- def create(self, *fields, **kw):
- """
- Create a new base with specified field names.
- Args:
- - \*fields (str): The field names to create.
- - mode (str): the mode used when creating the database.
- - if mode = 'create' : create a new base (the default value)
- - if mode = 'open' : open the existing base, ignore the fields
- - if mode = 'override' : erase the existing base and create a
- new one with the specified fields
- Returns:
- - the database (self).
- """
- self.mode = kw.get("mode", 'create')
- if self.save_to_file and os.path.exists(self.path):
- if not os.path.isfile(self.path):
- raise IOError("%s exists and is not a file" % self.path)
- elif self.mode is 'create':
- raise IOError("Base %s already exists" % self.path)
- elif self.mode == "open":
- return self.open()
- elif self.mode == "override":
- os.remove(self.path)
- else:
- raise ValueError("Invalid value given for 'open': '%s'" % open)
- self.fields = []
- self.default_values = {}
- for field in fields:
- if type(field) is dict:
- self.fields.append(field["name"])
- self.default_values[field["name"]] = field.get("default", None)
- elif type(field) is tuple:
- self.fields.append(field[0])
- self.default_values[field[0]] = field[1]
- else:
- self.fields.append(field)
- self.default_values[field] = None
- self.records = {}
- self.next_id = 0
- self.indices = {}
- self.commit()
- return self
- def create_index(self, *fields):
- """
- Create an index on the specified field names
- An index on a field is a mapping between the values taken by the field
- and the sorted list of the ids of the records whose field is equal to
- this value
- For each indexed field, an attribute of self is created, an instance
- of the class Index (see above). Its name it the field name, with the
- prefix _ to avoid name conflicts
- Args:
- - fields (list): the fields to index
- """
- reset = False
- for f in fields:
- if f not in self.fields:
- raise NameError("%s is not a field name %s" % (f, self.fields))
- # initialize the indices
- if self.mode == "open" and f in self.indices:
- continue
- reset = True
- self.indices[f] = {}
- for _id, record in self.records.items():
- # use bisect to quickly insert the id in the list
- bisect.insort(self.indices[f].setdefault(record[f], []), _id)
- # create a new attribute of self, used to find the records
- # by this index
- setattr(self, '_' + f, Index(self, f))
- if reset:
- self.commit()
可以看出,pydblite在内存中维护了一个名为records的字典变量,用来存放一条条的数据。它的key是内部维护的id,从0开始自增;而它的value则是用户插入的数据,为了后续查询和记录的方便,这里在每条数据中额外又加入了__id__和__version__。其次,内部维护的indices字典变量则是是个索引表,它的key是字段名,而value则是这样一个字典:其key是这个字段所有已知的值,value是这个值所在的那条数据的id。
举个例子,假设我们插入了“a=-1,b=0,c=1”和“a=0,b=1,c=2”两条数据,那么records和indices的内容会是这样的:
- # records
- {0: {'__id__': 0, '__version__': 0, 'a': -1, 'b': 0, 'c': 1},
- 1: {'__id__': 1, '__version__': 0, 'a': 0, 'b': 1, 'c': 2}}
- # indices
- {'a': {-1: [0], 0: [1]}, 'b': {0: [0], 1: [1]}}
比方说现在我们想查找a=0的数据,那么就会在indices中找key为'a'的value,即{-1: set([0]), 0: set([1])},然后在这里面找key为0的value,即[1],由此我们直到了我们想要的这条数据它的id是1(也可能会有多个);假设我们对数据还有其他要求比如a=0,b=1,那么它会继续上述的查找过程,找到a=0和b=1分别对应的ids,做交集,就得到了满足这两个条件的ids,然后再到records里根据ids找到所有对应的数据。
明白了原理,我们再看看有什么可优化的地方:
数据结构,整体的records和indeices数据结构已经挺精简了,暂时不需要优化。其中的__version__可以不要,因为我们并不关注这个数据被修改了几次。其次是由于indices中最终的ids是个list,在查询和插入的时候会比较慢,我们知道内部维护的id一定是唯一的,所以这里改成set会好一些。
python语句,不难看出,整个_Base为了同时兼容python2和python3,不得不使用了2和3都支持的语句,这就导致在部分语句上针对特定版本的python就会造成浪费或者说是性能开销。比如说,d是个字典,那么为了同事兼容python2和3,作者使用了类似与for key in d.keys()这样的语句,在python2中,d.keys()会首先产生一个list,用d.iterkeys是个更明智的方案。再如,作者会使用类似set(d.keys()) - set([1])这样的语句,但是python2中,使用d.viewkeys() - set([1])效率将会更高,因为它不需要将list转化成set。
对特定版本python的优化语句就不一一举例,概括地说,从数据结构,python语句以及是否需要某些功能等方面可以对pydblite做进一步的优化。前面只是说了create和create_index两个函数,包括insert和__call__的优化也十分类似。此外,用普通方法来代替魔法方法,也能稍微提升下效率,所以在后续的优化中将__call__改写为了query。
优化后的代码,请见MemLite。
4 memlite、pydblite和sqlite的性能
让我们在上文的测试代码中加入对memlite的测试:
- @timeit
- def test_query_method(mdb, des=''):
- for i in xrange(count):
- c = mdb.query(a=i-1, b=i)
- print '-------memlite-------'
- import memlite
- db = memlite.Base()
- db.create('a', 'b', 'c')
- db.create_index('a', 'b')
- test_insert(db, des='insert')
- test_query_method(db, des='query, method call')
在创建索引的情况下,10w次的插入和查询的时间如下:
- -------memlite-------
0.378000020981 insert
0.285000085831 query, method call
-------pydblite--------
1.3140001297 insert
0.309000015259 query, object call
-------sqlite3--------
0.414000034332 insert
0.3109998703 query
在未创建索引的情况(把创建索引的测试语句注释掉)下,1w次的插入和查询时间如下:
- -------memlite-------
- 0.0179998874664 insert
- 5.90199995041 query, method call
- -------pydblite--------
- 0.0980000495911 insert
- 4.87400007248 query, object call
- -------sqlite3--------
- 0.0170001983643 insert
- 7.42399978638 query
可以看出,在创建索引的情况下,memlite的插入和查询性能在sqlite和pydblite之上;而在未创建索引的情况下,memlite的插入性能和sqlite一样,好于pydblite,memlite的查询性能比pydblite稍差,但好于sqlite。综合来看,memlite即拥有pydblite的pythonic的使用方式,又拥有pydblite和sqlite中性能较高者的效率,符合预期的优化目标。
转载请注明出处:http://www.cnblogs.com/dreamlofter/p/5843355.html 谢谢!
Python内存数据库/引擎的更多相关文章
- python 内存数据库与远程服务
python 内存数据库与远程服务 需要import的python 内存数据库代码参考下面的链接: http://blog.csdn.net/ubuntu64fan/article/details/5 ...
- Airflow Python工作流引擎的重要概念介绍
Airflow Python工作流引擎的重要概念介绍 - watermelonbig的专栏 - CSDN博客https://blog.csdn.net/watermelonbig/article/de ...
- Python游戏引擎开发(七):绘制矢量图
今天来完毕绘制矢量图形. 没有读过前几章的同学,请先阅读前几章: Python游戏引擎开发(一):序 Python游戏引擎开发(二):创建窗体以及重绘界面 Python游戏引擎开发(三):显示图片 P ...
- Python游戏引擎开发(五):Sprite精灵类和鼠标事件
本次来实现Sprite类和鼠标事件. 说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它. 它中文原意是"精灵",只是在不同人的眼中,它所表示的意义不同. 比方说在 ...
- HiEngine:可媲美本地的云原生内存数据库引擎
摘要:HiEngine与华为GaussDB (for MySQL)集成,将内存数据库引擎的优势带到云端,并与基于磁盘的引擎共存.HiEngine的性能比传统的以存储为中心的解决方案高出7.5倍. 本文 ...
- python内存数据库pydblite
Pure-Python engine 最近由于项目开发中发现python informixDB模块对多线程的支持非常不好,当开启两个线程同时连接informix数据库的时候,数据库会报错,显示SQL ...
- H2:开源内存数据库引擎
本资源由 伯乐在线 - 刘立华 整理 H2是一个开源的内存数据库.Java编写.快速.小巧(1.5MB jar包)还提供了Web控制台管理数据库内容. 主要功能 非常快速的数据库引擎. 开源. Jav ...
- Python模板引擎Jinja2使用简介
原文链接 背景 最近在项目开发中,需要针对 Jenkins 项目进行配置,Jenkins 的 job 配置采用的是 xml,在维护配置模板的过程中就遇到了问题,因为逐步发现配置灵活性超出了字符串的范畴 ...
- 在windows上如何安装python web引擎jinja2
首先要把你的Python文件夹加到环境变量里头去.假设你的Python文件夹位于C:\Python34,那么你需要打开CMD并输入: SETX PATH "%path%;C:\Python3 ...
随机推荐
- 在win7环境下安装python2.6.6
Python2.x与3.x语法并不相同,这里装的是2.6.6的版本. 1.下载Python2.6.6: https://www.python.org/downloads/ 根据自身计算机的特点选择Py ...
- JSP页面组件
一.JSP指令 1.page指令 定义:将关于JSP页面一般设置通知给web容器的属性. 语法:<%@ page attribute_list%> 属性:language;extends; ...
- 继续向peersim的event-driven模式开火!(新手勿喷)
昨天学习了peersim的cycle模式,今天开始继续悟事件模式. 总的来说,我个人认为事件模式是周期模式的升级版,或者说,周期模式只是事件模式的一个子功能. 事件模式是基于时间和事件的(没打错),每 ...
- 【LeetCode OJ】Construct Binary Tree from Preorder and Inorder Traversal
Problem Link: https://oj.leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-trave ...
- Unity 摄像机组件
今天看一下unity3d里面的摄像机是怎么调用和操作的. 打开unity3d新建一个工程.在我们打开工程的时候unity3d会主动添加一个Main Camera,在Hierartchy视图中.点击Ma ...
- Unity3d游戏场景优化杂谈(2)
动态实时灯光相比静态灯光,非常耗费资源.所以除了能动的角色和物体(比如可以被打的到处乱飞的油桶)静态的地形和建筑,通通使用Lightmap. 强大的Unity内置了一个强大的光照图烘焙工具Beast, ...
- XenServer安全重启xapi的方法
XenServer安全重启xapi的方法 2012-11-29 12:58:07| 分类: 虚拟化-XenServer|字号 订阅 平常我们很常用到重启xapi命令,在这介绍下xapi: XAPI( ...
- 【JavaScript忍者秘籍】
- js或者ext js获取返回值
由于前台业务需要在判断中发起ajax到后台,根据返回值校验是否通过 代码如下 关键点在于要将async关闭 设置成同步,这样才能接收到要返回的flag ...
- dapper 学习
上一篇, 提到Query<Test>查询的时候, 如果Test中包含自定义class, Dapper不会给自定义class完成映射, 而是直接给null, 其实是可以实现的, 答案就在下面 ...