当前位置: 首页 > 科技观察

让Python更充分地使用Sqlite3

时间:2023-03-18 15:35:09 科技观察

最近在涉及大量数据处理的项目中经常使用sqlite3。我最初的尝试根本不涉及任何数据库,所有数据都将保存在内存中,包括字典查找、迭代和条件等查询。这很好,但内存中只能容纳这么多,并且从磁盘重新生成数据或将数据加载到内存是一个乏味且耗时的过程。我决定试试sqlite3。这增加了可以处理的数据量并将应用程序的加载时间减少到零,因为只需要打开与数据库的连接。另外,我可以用SQL查询替换很多Python逻辑语句。我想分享一些关于这次经历的想法和发现。TL;DR使用大量操作(又名executemany)。您不需要使用光标(大部分时间)。游标可以迭代。使用上下文管理器。使用编译指示(当它有意义时)。推迟索引创建。使用占位符插入python值。1.使用大量的操作如果你需要一次在数据库中插入很多行,那么你真的不应该使用execute。sqlite3模块提供了批量插入的方法:executemany。您可以利用executemany接受元组生成器作为参数这一事实,而不是像这样:forrowiniter_data():connection.execute('INSERTITOmy_tableVALUES(?)',row)',iter_data())这样不仅更简洁,而且效率更高。实际上,sqlite3在后台使用executemany实现了execute,只是后者插入的是一行而不是多行。我写了一个小的基准测试,将100万行插入一个空表(内存中的数据库):executemany:1.6秒execute:2.7秒2.你不需要游标一开始我总是感到困惑的是,游标管理。在在线示例和文档中,它通常是这样的:connection=sqlite3.connect(':memory:')cursor=connection.cursor()#Dosomethingwithcursor但是大多数时候,你根本不需要游标,你可以使用直接连接对象。可以直接在连接上调用诸如executeexecutemany之类的操作。这是一个演示此操作的示例:importsqlite3connection=sqlite3(':memory:')#Createatableconnection.execute('CREATETABLEevents(ts,msg)')#Insertvaluesconnection.executemany('INSERTINTOeventsVALUES(?,?)',[(1,'foo'),(2,'bar'),(3,'baz')])#Printinsertedrowsforrowinconnnection.execute('SELECT*FROMevents'):print(row)3.游标可以用于迭代你可能经常看到例子使用fetchone或fetchall处理SELECT查询的结果。但我发现处理这些结果最自然的方法是直接在游标上迭代:forrowinconnection.execute('SELECT*FROMevents'):print(row)这样,一旦获得足够的结果,就可以终止查询,并且不会造成资源浪费。当然,如果您事先知道需要多少结果,则可以改用LIMITSQL语句,但是Python生成器非常方便,可以让您将数据生成与数据消费分离开来。4.使用上下文管理器(ContextManager)即使在处理SQL事务的中间,讨厌的事情也会发生。为避免手动处理回滚或提交,您可以简单地将连接对象用作上下文管理器。在下面的示例中,我们创建了一个表并错误地插入了重复值:importsqlite3connection=sqlite3.connect(':memory:')withconnection:connection.execute('CREATETABLEevents(ts,msg,PRIMARYKEY(ts,msg))')try:withconnection:connection.executemany('INSERTINTOeventsVALUES(?,?)',[(1,'foo'),(2,'bar'),(3,'baz'),(1,'foo'),])except(sqlite3.OperationalError,sqlite3.IntegrityError)ase:print('Couldnotcompleteoperation:',e)#Norowwasinsertedbecausetransactionfailedforrowinconnection.execute('SELECT*FROMevents'):print(row)connection.close()5.使用Pragmas...当它真正有用时有几个pragmas可用于调整程序中sqlite3的行为。特别是,可以提高性能的事情之一是同步:connection.execute('PRAGMAsynchronous=OFF')您应该意识到这可能很危险。如果应用程序在事务处理过程中意外崩溃,数据库可能会处于不一致状态。所以请谨慎使用!但是,如果您要更快地插入很多行,那么这可能是一个选择。6.延迟创建索引假设你需要在数据库上创建多个索引,并且需要在插入很多行的同时创建索引。将索引创建推迟到所有行都已插入之后,可以显着提高性能。7.使用占位符插入Python值使用Python字符串操作将值包含到查询中很方便。但这是非常不安全的,sqlite3为您提供了更好的方法来执行此操作:#Donotdothis!my_timestamp=1c.execute("SELECT*FROMeventsWHEREts='%s'"%my_timestamp)#Dothisinsteadmy_timestamp=(1,)c.execute('SELECT*FROMeventsWHEREts=?',my_timestamp)此外,executemany并不总是可以使用Python%s(或格式或格式中的字符串常量)进行字符串插值。所以在这里尝试是没有意义的!请记住,这些小技巧可能(也可能不会)使您受益,具体取决于具体用例。您应该始终亲自尝试并决定是否值得。