热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python内存数据库/引擎

1初探在平时的开发工作中,我们可能会有这样的需求:我们希望有一个内存数据库或者数据引擎,用比较Pythonic的方式进行数据库的操作(比如说插入和查询)。举个具体的例子,分别向数据库db

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 谢谢!


推荐阅读
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • Day2列表、字典、集合操作详解
    本文详细介绍了列表、字典、集合的操作方法,包括定义列表、访问列表元素、字符串操作、字典操作、集合操作、文件操作、字符编码与转码等内容。内容详实,适合初学者参考。 ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
author-avatar
qlb
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有