在数据库被锁时重试SQLite查询的最简单方法是什么?
我不太确定该在哪里提问,希望这里合适。
我想找一个最简单的方法来重试一个SQLite查询,如果数据库正在忙的话。我在服务器上用quassel作为我的IRC客户端,我想把旧的日志移动到一个单独的数据库里,以保持当前使用的那个数据库小一些。我写的脚本是:
CREATE TEMP TABLE delfrom (id integer,val integer);
ATTACH '/home/irc/oldlog.db' as log;
BEGIN IMMEDIATE;
REPLACE INTO delfrom (id,val) select 1337,messageid from backlog where time < strftime('%s', 'now','-14 days') ORDER BY messageid DESC LIMIT 1;
INSERT INTO log.log (messageid,time,bufferid,type,flags,senderid,message) SELECT messageid,time,bufferid,type,flags,senderid,message FROM backlog WHERE messageid < (SELECT val FROM delfrom where id=1337);
DELETE FROM backlog WHERE messageid < (SELECT val FROM delfrom where id=1337);
PRAGMA incremental_vacuum;
COMMIT;
我用命令sqlite3 quassel-storage.sqlite < movelog.sql来运行它。
问题是,因为quassel在执行这个脚本的时候有在运行,所以有时候BEGIN IMMEDIATE;
会失败,因为数据库被锁住了。
有没有人能给我一个简单的方法来修改这个设置,让查询每隔几秒就重试一次,直到成功为止?我听说Python的SQLite库里有这个功能?我需要特别的方式来激活它吗?更重要的是,我能用Python连接第二个数据库吗?sqlite3.connect
有一个超时参数,但我不太明白它是怎么工作的。Python在每次连接时会锁住整个数据库吗?
我并不一定要用Python。我更希望的解决方案是,当出现这个错误时,sqlite3返回0,然后在命令行里用一个循环包裹起来,但这似乎不行。
3 个回答
如果你把超时时间设置得足够长,SQLite库会定期尝试重新连接。
在默认的Python接口中,这个超时时间是sqlite3.connect函数的第二个参数。
如果你的Sqlite版本大于3.7,建议使用WAL模式。你可以查看这个链接了解更多信息:https://www.sqlite.org/wal.html
connect = sqlite3.connect(DB, **kwargs)
connect.execute("PRAGMA journal_mode=WAL")
根据文档的说明,"WAL模式可以让多个操作同时进行,读取数据的操作不会阻止写入数据的操作,写入数据的操作也不会阻止读取数据的操作。这样,读取和写入可以同时进行。"
如果数据库中的某个表被锁住,Python会定期尝试重新连接。但如果是数据库被锁住,它就不会再尝试了。表的锁只会在同一个进程内部传播,比如通过线程、共享连接或其他方法。
数据库锁是因为多个进程同时写入文件造成的,简单来说,只要日志存在,锁就会一直存在。
为了避免这种情况,可以使用WAL模式来进行日志记录。(可以用命令:pragma journal_mode=wal;)
如果想要在数据库锁住的情况下继续尝试执行操作,可以把执行函数包裹成这样:
for x in range(0, timeout):
try:
with connection:
connection.execute(sql)
except:
time.sleep(1)
pass
finally:
break
else:
with connection:
connection.execute(sql)
最后的连接块会让它正确返回异常。其实可以进一步改进,检查异常是否是数据库被锁住,如果是就处理一下,不是的话就抛出原来的异常,不过这个留给读者自己去做。