python使用轻量级数据库--tinydb

文档:https://tinydb.readthedocs.io/en/latest/usage.html

Github:https://github.com/msiemens/tinydb

TinyDB 是一个纯 Python 编写的轻量级数据库,一共只有1800行代码,没有外部依赖项。

TinyDB的目标是降低小型 Python 应用程序使用数据库的难度,对于一些简单程序而言与其用 SQL 数据库,不如就用TinyDB, 因为它有如下特点:

轻便:当前源代码有 1800 行代码(大约 40% 的文档)和 1600 行测试代码。

可随意迁移:在当前文件夹下生成数据库文件,不需要任何服务,可以随意迁移。

简单:TinyDB 通过提供简单干净的 API 使得用户易于使用。

用纯 Python 编写:TinyDB 既不需要外部服务器,也不需要任何来自 PyPI 的依赖项。

适用于 Python 3.6+ 和 PyPy3:TinyDB 适用于所有现代版本的 Python 和 PyPy。

强大的可扩展性:您可以通过编写中间件修改存储的行为来轻松扩展 TinyDB。

100% 测试覆盖率:无需解释。

基础使用

安装:pip install tinydb

导入使用:from tinydb import TinyDB, Query

初始化数据库:db=TinyDB('db.json')

初始化条件查询:Fruit = Query()

注:在初始化后,在指定目录生成文件,用于存储数据,query用于条件查询使用

常用格式如下:

插入   db.insert(...) Insert a document  在TinyDB内部,插入的每个文档都关联一个document ID。它在插入文档后返回。

获取数据    db.all() 获取文档所有内容    iter(db) Iter over all documents    db.search(query) 获取与查询匹配的文档列表

更新数据    db.update(fields, query)  以字段更新与查询匹配的文档内容

删除  db.remove(query) 删除匹配的文档内容    db.truncate() 删除文档所有

条件使用

Query() 创建一个查询对象

Query().field == 2 匹配具有关键字段的任何文档 value == 2 (also possible: !=, >, >=, <, <=)

数据增删改查:

增:db.insert({'name':'L1','value':'18'})

删:db.remove(Fruit.name== ‘L1’)

改:db.update({'value':'24'},Fruit.name== 'Y1')

查:db.search(Fruit.name == 'L1')

展示文档中所有数据:db.all()

清空文档中所有数据:db.truncate()

查询扩展

Query().field.exists() 示例:db.search(User.name.exists())

判断是否存在名为field的字段

Query().field.matches(regex)   示例:db.search(User.name.matches('[aZ]*'))

用匹配正则表达式的整个字段匹配任何文档,是否存在regex相同的字段

Query().field.search(regex)  示例:db.search(User.name.search('b+'))

用匹配正则表达式的字段的子字符串匹配任何文档,字段中是否存在符合的regex字符串

Query().field.test(func, *args)   示例:db.search(User.age.test(test_func, 0, 21))

匹配函数返回True的任何文档,args为向fun中传递的参数值

Query().field.all(query | list)   示例:db.search(User.groups.all(['admin', 'user']))

如果给定一个查询,则匹配列表字段中的所有文档都与查询匹配的所有文档。

如果给定一个列表,则匹配列表字段中的所有文档都是给定列表的成员的所有文档

Query().field.any(query | list)   示例:db.search(User.groups.any(['admin', 'sudo']))

如果给定一个查询,则匹配列表字段中至少有一个文档与查询匹配的所有文档。

如果给定一个列表,则匹配列表字段中至少有一个文档是给定列表成员的所有文档

Query().field.one_of(list)  示例:db.search(User.name.one_of(['jane', 'john']))

如果字段包含在列表中,则进行匹配

查询的逻辑操作

~ (query)             与查询不匹配的文档

(query1) & (query2) 匹配两个查询的文档

(query1) | (query2)     至少匹配其中一个查询的文档

增删改扩展

db.insert_multiple(...)   插入多个文档

示例:

插入多条:db.insert_multiple([{'name':'L1','value':'21'},{'name':'S1','value':'21'}])

循环插入数组中多条:db.insert_multiple({'name': 'L1', 'value': i} for i in range(2))

根据ID插入:db.insert(Document({'name': 'L1', 'value': '21'},doc_id=12))

db.update(operation, ...)  用特殊操作更新所有匹配的文档

当将字典传递给db时。更新(字段,查询),它只允许通过添加或覆盖其值来更新文档。

但有时可能需要删除一个字段或增加它的值。在这种情况下,你可以传递一个函数而不是字段,TinyDB自带这些操作:

导入方法:from tinydb.operations import *

delete (key):删除文档中的一个密钥  

increment (key):增加键的值

decrement (key):减少键值

add (key, value):将值添加到键的值(也适用于字符串)

subtract (key, value):从键的值中减去值

set (key, value):将key设置为value

示例:db.update_multiple([(delete('value'), where('name') == 'S1'),({'value':'11'}, where('name') == 'L1')])

db.upsert()

在某些情况下,需要同时使用update和insert: upsert。该操作提供了一个文档和一个查询。

如果它发现任何与查询匹配的文档,则将使用所提供文档中的数据更新这些文档。

另一方面,如果没有找到匹配的文档,它会将提供的文档插入到表中:

示例:db.upsert({'name': 'L2', 'value': '21'},where('name') == 'L2')

有几种方法可以从数据库中检索数据。例如:

获取存储文档的数量: len(db)

获取指定文档: db.get(where('name') == 'L2')

注:如果多个文档与查询匹配,可能会返回其中一个随机的文档!

判断文档是否存在: db.contains(where('name') == 'L2')

统计文档的数量: db.count(where('name') == 'L2')

Document id的使用

在TinyDB内部,插入的每个文档都关联一个ID。它在插入文档后返回:

print_value(db.insert({'name':'S2','value':'16'}))

此外,可以使用document.doc_id获取已插入文档的ID。这对get和all都有效:

db.get(where('name') == 'L2').doc_id

db.all()[-1].doc_id

db.all()[0].doc_id

不同的TinyDB方法也适用于id,即:更新,删除,包含和获取。前两个还返回受影响id的列表。

db.update({'value': 2}, doc_ids=[1, 2])

db.contains(doc_id=1)

db.remove(doc_ids=[1, 2])

db.get(doc_id=3)

表使用

TinyDB支持使用多个表。它们的行为与TinyDB类相同。

要创建和使用表,请使用db.table(name)。

创建:table = db.table('table_name')

插入数据:table.insert({'value': True})

输出表数据:[print (tb) for tb in table]

获取表数据:table.all()

删除:db.drop_table('table_name')

删除所有:db.drop_tables()

获取所有的表:db.tables()

TinyDB使用一个名为_default的表作为默认表。

所有对数据库对象的操作(如db.insert(…))都在这个表上操作。

这个表的名字可以通过设置default_table_name类变量来修改所有实例的默认表名:

db = TinyDB(storage=SomeStorage)

db.default_table_name = 'my-default'

或者 TinyDB.default_table_name = 'my-default'

TinyDB缓存性能查询结果。这样,只要数据库没有被修改,重新运行查询就不必从存储中读取数据。

你可以通过将cache_size传递给table(…)函数来优化查询缓存大小:

table = db.table('table_name', cache_size=30)

注:可以将cache_size设置为None,使缓存大小无限制。此外,可以将cache_size设置为0来禁用它。

注:不可能使用不同的设置多次打开同一个表。在第一次调用之后,所有后续调用都将返回与第一次调用具有相同设置的同一个表。

注:TinyDB查询缓存不会检查数据库使用的底层存储是否已被外部进程修改。

在这种情况下,查询缓存可能返回过时的结果。要清除缓存并再次从存储中读取数据,可以使用db.clear_cache()。

注:当使用无限缓存大小和test()查询时,TinyDB将存储对test函数的引用。

由于这种行为,使用lambda函数作为测试函数的长时间运行的应用程序可能会出现内存泄漏。

存储类型

TinyDB有两种存储类型:JSON和内存中。

默认情况下,TinyDB将其数据存储在JSON文件中,所以你必须指定存储它的路径:

db = TinyDB('path/to/db.json')

要使用内存存储,使用:

db = TinyDB(storage=MemoryStorage)

注:除storage参数外的所有参数都被转发到底层存储。

对于JSON存储,可以使用它将其他关键字参数传递给Python的JSON .dump(…)方法。

例如,可以设置它来创建美化的JSON文件,如:

db = TinyDB('db.json', sort_keys=True, indent=4, separators=(',', ': '))

要修改所有TinyDB实例的默认存储,设置default_storage_class类变量:

TinyDB.default_storage_class = MemoryStorage

如果需要直接访问存储实例,可以使用TinyDB实例的存储属性。这对于直接在存储或中间件上调用方法可能很有用:

db = TinyDB(storage=CachingMiddleware(MemoryStorage))

db.storage.flush()

中间件使用

中间件围绕现有的存储,允许自定义它们的行为。

from tinydb.storages import JSONStorage

from tinydb.middlewares import CachingMiddleware

db = TinyDB('/path/to/db.json', storage=CachingMiddleware(JSONStorage))

可以嵌套中间件:

db = TinyDB('/path/to/db.json',storage=FirstMiddleware(SecondMiddleware(JSONStorage)))

读写安全

CachingMiddleware通过减少磁盘I/O来提高速度。它缓存所有读操作,并在配置的写操作次数后将数据写入磁盘。

为了确保关闭表时所有数据都被安全写入,请使用以下方法之一:

  1. with database as db:
  2. db.close()

插件使用

TinyDB带有类型注释,MyPy可以使用它来确保你正确地使用API。

不幸的是,MyPy并不理解TinyDB使用的所有代码模式。

出于这个原因,TinyDB发布了一个myypy插件,帮助正确地检查使用TinyDB的代码。

要使用它,将它添加到myypy配置文件中的插件列表(通常位于setup.cfg或MyPy .ini中):

[mypy]

plugins = tinydb.mypy_plugin

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
数据库的名字叫WawaDB,是用python实现的。由此可见python是灰常强大啊! 简介 记录日志的需求一般是这样的: 只追加,不修改,写入按时间顺序写入; 大量写,少量读,查询一般查询一个时间段的数据; MongoDB的固定集合很好的满足了这个需求,但是MongoDB占内存比较大,有点儿火穿蚊子,小题大做的感觉。 WawaDB的思路是每写入1000条日志,在一个索引文件里记录下当前的时间和日志文件的偏移量。 然后按时间询日志时,先把索引加载到内存中,用二分法查出时间点的偏移量,再打开日志文件seek到指定位置,这样就能很快定位用户需要的数据并读取,而不需要遍历整个日志文件。 性能 Core 2 P8400,2.26GHZ,2G内存,32 bit win7 写入测试: 模拟1分钟写入10000条数据,共写入5个小时的数据, 插入300万条数据,每条数据54个字符,用时2分51秒 读取测试:读取指定时间段内包含某个子串的日志 数据范围 遍历数据量 结果数 用时(秒) 5小时 300万 604 6.6 2小时 120万 225 2.7 1小时 60万 96 1.3 30分钟 30万 44 0.6 索引 只对日志记录的时间做索引, 简介里大概说了下索引的实现,二分查找肯定没B Tree效率高,但一般情况下也差不了一个数量级,而且实现特别简单。 因为是稀疏索引,并不是每条日志都有索引记录它的偏移量,所以读取数据时要往前多读一些数据,防止漏读,等读到真正所需的数据时再真正给用户返回数据。 如下图,比如用户要读取25到43的日志,用二分法找25,找到的是30所在的点, 索 引:0 10 20 30 40 50 日志:|.........|.........|.........|.........|.........|>>>a = [0, 10, 20, 30, 40, 50]>>>bisect.bisect_left(a, 35)>>>3>>>a[3]>>>30>>>bisect.bisect_left(a, 43)>>>5>>>a[5]>>>50 所以我们要往前倒一些,从20(30的前一个刻度)开始读取日志,21,22,23,24读取后因为比25小,所以扔掉, 读到25,26,27,...后返回给用户 读取到40(50的前一个刻度)后就要判断当前数据是否大于43了,如果大于43(返回全开区间的数据),就要停止读了。 整体下来我们只操作了大文件的很少一部分就得到了用户想要的数据。 缓冲区 为了减少写入日志时大量的磁盘写,索引在append日志时,把buffer设置成了10k,系统默认应该是4k。 同理,为了提高读取日志的效率,读取的buffer也设置了10k,也需要根据你日志的大小做适当调整。 索引的读写设置成了行buffer,每满一行都要flush到磁盘上,防止读到不完整的索引行(其实实践证明,设置了行buffer,还是能读到半拉的行)。 查询 啥?要支持SQL,别闹了,100行代码怎么支持SQL呀。 现在查询是直接传入一个lambada表达式,系统遍历指定时间范围内的数据行时,满足用户的lambada条件才会返回给用户。 当然这样会多读取很多用户不需要的数据,而且每行都要进行lambda表达式的运算,不过没办法,简单就是美呀。 以前我是把一个需要查询的条件和日志时间,日志文件偏移量都记录在索引里,这样从索引里查找出符合条件的偏移量,然后每条数据都如日志文件里seek一次,read一次。这样好处只有一个,就是读取的数据量少了,但缺点有两个: 索引文件特别大,不方便加载到内存中 每次读取都要先seek,貌似缓冲区用不上,特别慢,比连续读一个段的数据,并用lambda过滤慢四五倍 写入 前面说过了,只append,不修改数据,而且每行日志最前面是时间戳。 多线程 查询数据,可以多线程同时查询,每次查询都会打开一个新的日志文件的描述符,所以并行的多个读取不会打架。 写入的话,虽然只是append操作,但不确认多线程对文件进行append操作是否安全,所以建议用一个队列,一个专用线程进行写入。 锁 没有任何锁。 排序 默认查询出来的数据是按时间正序排列,如需其它排序,可取到内存后用python的sorted函数排序,想怎么排就怎么排。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值