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

  1. import pydblite
  2. # 使用内存数据库
  3. pydb = pydblite.Base(':memory:')
  4. # 创建a,b,c三个字段
  5. pydb.create('a', 'b', 'c')
  6. # 为字段a,b创建索引
  7. pydb.create_index('a', 'b')
  8. # 插入一条数据
  9. pydb.insert(a=-1, b=0, c=1)
  10. # 查询符合特定要求的数据
  11. results = pydb(a=-1, b=0)

SQLite

  1. import sqlite3
  2. # 使用内存数据库
  3. con = sqlite3.connect(':memory:')
  4. # 创建a,b,c三个字段
  5. cur = con.cursor()
  6. cur.execute('create table test (a char(256), b char(256), c char(256));')
  7. # 为字段a,b创建索引
  8. cur.execute('create index a_index on test(a)')
  9. cur.execute('create index b_index on test(b)')
  10. # 插入一条数据
  11. cur.execute('insert into test values(?, ?, ?)', (-1,0,1))
  12. # 查询符合特定要求的数据
  13. cur.execute('select * from test where a=? and b=?',(-1, 0))

2 pydblite和sqlite的性能

  毫无疑问,pydblite的使用方式非常地Pythonic,但是它的效率如何呢?由于我们主要关心的是数据插入和查询速度,所以不妨仅对这两项做一个对比。写一个简单的测试脚本:

  1. import time
  2. count = 100000
  3.  
  4. def timeit(func):
  5. def wrapper(*args, **kws):
  6. t = time.time()
  7. func(*args)
  8. print time.time() - t, kws['des']
  9. return wrapper
  10.  
  11. @timeit
  12. def test_insert(mdb, des=''):
  13. for i in xrange(count):
  14. mdb.insert(a=i-1, b=i, c=i+1)
  15.  
  16. @timeit
  17. def test_query_object(mdb, des=''):
  18. for i in xrange(count):
  19. c = mdb(a=i-1, b=i)
  20.  
  21. @timeit
  22. def test_sqlite_insert(cur, des=''):
  23. for i in xrange(count):
  24. cur.execute('insert into test values(?, ?, ?)', (i-1, i, i+1))
  25.  
  26. @timeit
  27. def test_sqlite_query(cur, des=''):
  28. for i in xrange(count):
  29. cur.execute('select * from test where a=? and b=?', (i-1, i))
  30.  
  31. print '-------pydblite--------'
  32. import pydblite
  33. pydb = pydblite.Base(':memory:')
  34. pydb.create('a', 'b', 'c')
  35. pydb.create_index('a', 'b')
  36. test_insert(pydb, des='insert')
  37. test_query_object(pydb, des='query, object call')
  38.  
  39. print '-------sqlite3--------'
  40. import sqlite3
  41. con = sqlite3.connect(':memory:')
  42. cur = con.cursor()
  43. cur.execute('create table test (a char(256), b char(256), c char(256));')
  44. cur.execute('create index a_index on test(a)')
  45. cur.execute('create index b_index on test(b)')
  46. test_sqlite_insert(cur, des='insert')
  47. test_sqlite_query(cur, des='query')

  在创建索引的情况下,10w次的插入和查询的时间如下:

  1. -------pydblite--------
    1.14199995995 insert
    0.308000087738 query, object call
    -------sqlite3--------
    0.411999940872 insert
    0.30999994278 query

  在未创建索引的情况(把创建索引的测试语句注释掉)下,1w次的插入和查询时间如下:

  1. -------pydblite--------
  2. 0.0989999771118 insert
  3. 5.15300011635 query, object call
  4. -------sqlite3--------
  5. 0.0169999599457 insert
  6. 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这个类。

  1. class _BasePy2(_Base):
  2.  
  3. def __iter__(self):
  4. """Iteration on the records"""
  5. return iter(self.records.itervalues())
  6.  
  7. class _BasePy3(_Base):
  8.  
  9. def __iter__(self):
  10. """Iteration on the records"""
  11. return iter(self.records.values())
  12.  
  13. if sys.version_info[0] == 2:
  14. Base = _BasePy2
  15. else:
  16. Base = _BasePy3

  然后看下_Base的构造函数,做了简单的初始化文件的操作,由于我们就是使用内存数据库,所以文件相关的内容完全可以抛弃。

  1. class _Base(object):
  2.  
  3. def __init__(self, path, protocol=pickle.HIGHEST_PROTOCOL, save_to_file=True,
  4. sqlite_compat=False):
  5. """protocol as defined in pickle / pickle.
  6. Defaults to the highest protocol available.
  7. For maximum compatibility use protocol = 0
  8.  
  9. """
  10. self.path = path
  11. """The path of the database in the file system"""
  12. self.name = os.path.splitext(os.path.basename(path))[0]
  13. """The basename of the path, stripped of its extension"""
  14. self.protocol = protocol
  15. self.mode = None
  16. if path == ":memory:":
  17. save_to_file = False
  18. self.save_to_file = save_to_file
  19. self.sqlite_compat = sqlite_compat
  20. self.fields = []
  21. """The list of the fields (does not include the internal
  22. fields __id__ and __version__)"""
  23. # if base exists, get field names
  24. if save_to_file and self.exists():
  25. if protocol == 0:
  26. _in = open(self.path) # don't specify binary mode !
  27. else:
  28. _in = open(self.path, 'rb')
  29. self.fields = pickle.load(_in)

  紧接着比较重要的是create(创建字段)、create_index(创建索引)两个函数:

  1. def create(self, *fields, **kw):
  2. """
  3. Create a new base with specified field names.
  4.  
  5. Args:
  6. - \*fields (str): The field names to create.
  7. - mode (str): the mode used when creating the database.
  8.  
  9. - if mode = 'create' : create a new base (the default value)
  10. - if mode = 'open' : open the existing base, ignore the fields
  11. - if mode = 'override' : erase the existing base and create a
  12. new one with the specified fields
  13.  
  14. Returns:
  15. - the database (self).
  16. """
  17. self.mode = kw.get("mode", 'create')
  18. if self.save_to_file and os.path.exists(self.path):
  19. if not os.path.isfile(self.path):
  20. raise IOError("%s exists and is not a file" % self.path)
  21. elif self.mode is 'create':
  22. raise IOError("Base %s already exists" % self.path)
  23. elif self.mode == "open":
  24. return self.open()
  25. elif self.mode == "override":
  26. os.remove(self.path)
  27. else:
  28. raise ValueError("Invalid value given for 'open': '%s'" % open)
  29.  
  30. self.fields = []
  31. self.default_values = {}
  32. for field in fields:
  33. if type(field) is dict:
  34. self.fields.append(field["name"])
  35. self.default_values[field["name"]] = field.get("default", None)
  36. elif type(field) is tuple:
  37. self.fields.append(field[0])
  38. self.default_values[field[0]] = field[1]
  39. else:
  40. self.fields.append(field)
  41. self.default_values[field] = None
  42.  
  43. self.records = {}
  44. self.next_id = 0
  45. self.indices = {}
  46. self.commit()
  47. return self
  48.  
  49. def create_index(self, *fields):
  50. """
  51. Create an index on the specified field names
  52.  
  53. An index on a field is a mapping between the values taken by the field
  54. and the sorted list of the ids of the records whose field is equal to
  55. this value
  56.  
  57. For each indexed field, an attribute of self is created, an instance
  58. of the class Index (see above). Its name it the field name, with the
  59. prefix _ to avoid name conflicts
  60.  
  61. Args:
  62. - fields (list): the fields to index
  63. """
  64. reset = False
  65. for f in fields:
  66. if f not in self.fields:
  67. raise NameError("%s is not a field name %s" % (f, self.fields))
  68. # initialize the indices
  69. if self.mode == "open" and f in self.indices:
  70. continue
  71. reset = True
  72. self.indices[f] = {}
  73. for _id, record in self.records.items():
  74. # use bisect to quickly insert the id in the list
  75. bisect.insort(self.indices[f].setdefault(record[f], []), _id)
  76. # create a new attribute of self, used to find the records
  77. # by this index
  78. setattr(self, '_' + f, Index(self, f))
  79. if reset:
  80. 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的内容会是这样的:

  1. # records
  2. {0: {'__id__': 0, '__version__': 0, 'a': -1, 'b': 0, 'c': 1},
  3. 1: {'__id__': 1, '__version__': 0, 'a': 0, 'b': 1, 'c': 2}}
  4.  
  5. # indices
  6. {'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的测试:

  1. @timeit
  2. def test_query_method(mdb, des=''):
  3. for i in xrange(count):
  4. c = mdb.query(a=i-1, b=i)
  5.  
  6. print '-------memlite-------'
  7. import memlite
  8. db = memlite.Base()
  9. db.create('a', 'b', 'c')
  10. db.create_index('a', 'b')
  11. test_insert(db, des='insert')
  12. test_query_method(db, des='query, method call')

在创建索引的情况下,10w次的插入和查询的时间如下:

  1. -------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次的插入和查询时间如下:

  1. -------memlite-------
  2. 0.0179998874664 insert
  3. 5.90199995041 query, method call
  4. -------pydblite--------
  5. 0.0980000495911 insert
  6. 4.87400007248 query, object call
  7. -------sqlite3--------
  8. 0.0170001983643 insert
  9. 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内存数据库/引擎的更多相关文章

  1. python 内存数据库与远程服务

    python 内存数据库与远程服务 需要import的python 内存数据库代码参考下面的链接: http://blog.csdn.net/ubuntu64fan/article/details/5 ...

  2. Airflow Python工作流引擎的重要概念介绍

    Airflow Python工作流引擎的重要概念介绍 - watermelonbig的专栏 - CSDN博客https://blog.csdn.net/watermelonbig/article/de ...

  3. Python游戏引擎开发(七):绘制矢量图

    今天来完毕绘制矢量图形. 没有读过前几章的同学,请先阅读前几章: Python游戏引擎开发(一):序 Python游戏引擎开发(二):创建窗体以及重绘界面 Python游戏引擎开发(三):显示图片 P ...

  4. Python游戏引擎开发(五):Sprite精灵类和鼠标事件

    本次来实现Sprite类和鼠标事件. 说起这个Sprite啊,涉及过2D游戏研究领域的看官应该都听说过它. 它中文原意是"精灵",只是在不同人的眼中,它所表示的意义不同. 比方说在 ...

  5. HiEngine:可媲美本地的云原生内存数据库引擎

    摘要:HiEngine与华为GaussDB (for MySQL)集成,将内存数据库引擎的优势带到云端,并与基于磁盘的引擎共存.HiEngine的性能比传统的以存储为中心的解决方案高出7.5倍. 本文 ...

  6. python内存数据库pydblite

    Pure-Python engine 最近由于项目开发中发现python informixDB模块对多线程的支持非常不好,当开启两个线程同时连接informix数据库的时候,数据库会报错,显示SQL ...

  7. H2:开源内存数据库引擎

    本资源由 伯乐在线 - 刘立华 整理 H2是一个开源的内存数据库.Java编写.快速.小巧(1.5MB jar包)还提供了Web控制台管理数据库内容. 主要功能 非常快速的数据库引擎. 开源. Jav ...

  8. Python模板引擎Jinja2使用简介

    原文链接 背景 最近在项目开发中,需要针对 Jenkins 项目进行配置,Jenkins 的 job 配置采用的是 xml,在维护配置模板的过程中就遇到了问题,因为逐步发现配置灵活性超出了字符串的范畴 ...

  9. 在windows上如何安装python web引擎jinja2

    首先要把你的Python文件夹加到环境变量里头去.假设你的Python文件夹位于C:\Python34,那么你需要打开CMD并输入: SETX PATH "%path%;C:\Python3 ...

随机推荐

  1. 在win7环境下安装python2.6.6

    Python2.x与3.x语法并不相同,这里装的是2.6.6的版本. 1.下载Python2.6.6: https://www.python.org/downloads/ 根据自身计算机的特点选择Py ...

  2. JSP页面组件

    一.JSP指令 1.page指令 定义:将关于JSP页面一般设置通知给web容器的属性. 语法:<%@ page attribute_list%> 属性:language;extends; ...

  3. 继续向peersim的event-driven模式开火!(新手勿喷)

    昨天学习了peersim的cycle模式,今天开始继续悟事件模式. 总的来说,我个人认为事件模式是周期模式的升级版,或者说,周期模式只是事件模式的一个子功能. 事件模式是基于时间和事件的(没打错),每 ...

  4. 【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 ...

  5. Unity 摄像机组件

    今天看一下unity3d里面的摄像机是怎么调用和操作的. 打开unity3d新建一个工程.在我们打开工程的时候unity3d会主动添加一个Main Camera,在Hierartchy视图中.点击Ma ...

  6. Unity3d游戏场景优化杂谈(2)

    动态实时灯光相比静态灯光,非常耗费资源.所以除了能动的角色和物体(比如可以被打的到处乱飞的油桶)静态的地形和建筑,通通使用Lightmap. 强大的Unity内置了一个强大的光照图烘焙工具Beast, ...

  7. XenServer安全重启xapi的方法

    XenServer安全重启xapi的方法 2012-11-29 12:58:07|  分类: 虚拟化-XenServer|字号 订阅 平常我们很常用到重启xapi命令,在这介绍下xapi: XAPI( ...

  8. 【JavaScript忍者秘籍】

  9. js或者ext js获取返回值

      由于前台业务需要在判断中发起ajax到后台,根据返回值校验是否通过 代码如下 关键点在于要将async关闭 设置成同步,这样才能接收到要返回的flag                       ...

  10. dapper 学习

    上一篇, 提到Query<Test>查询的时候, 如果Test中包含自定义class, Dapper不会给自定义class完成映射, 而是直接给null, 其实是可以实现的, 答案就在下面 ...