如何处理sqlite3.OperationalError: database is locked错误
场景与代码
应用为长期运行的多进程服务,逻辑是从SQLite查询任务→执行HTTP请求→更新任务结果。单进程运行正常,多进程并行时出现sqlite3.OperationalError: database is locked,未处理会导致进程崩溃;添加异常捕获后出现死锁,两进程均无法继续查询,杀死一个进程后恢复正常。
原始单进程代码:
database = sqlite3.connect("path to database") for job in range(1000): for row in database.execute("SELECT something"): database.execute("UPDATE something for the row") database.commit()
异常捕获后的代码(出现死锁):
database = sqlite3.connect("path to database") for job in range(1000): for row in database.execute("SELECT something"): try: # process row database.execute("UPDATE something for the row") database.commit() except sqlite3.OperationalError as e: # wait for some time to allow other processes to complete the transaction
需求:是否存在无需关闭连接即可解锁数据库的API?若没有,如何正确处理锁异常避免死锁?
解决方案
核心结论
SQLite没有无需关闭连接即可解锁的官方API。当出现database is locked异常时,当前连接的事务状态已处于不稳定状态,必须重置连接才能恢复正常操作。
具体处理步骤
- 捕获异常时强制回滚+重连
锁异常发生后,先回滚当前事务(避免残留未提交的操作),然后关闭旧连接并创建新连接,不要复用异常连接:def get_db_connection(): return sqlite3.connect("path to database", timeout=30) # 设置超时等待锁释放
db_conn = get_db_connection()
for job in range(1000):
try:
# 每次循环前检查连接状态,若失效则重连
if db_conn is None or not db_conn.cursor():
db_conn = get_db_connection()
for row in db_conn.execute("SELECT something"):
try:
# process row(执行HTTP请求等操作)
db_conn.execute("UPDATE something for the row")
db_conn.commit()
except sqlite3.OperationalError as e:
if "database is locked" in str(e):
# 回滚事务
db_conn.rollback()
# 关闭旧连接并重新创建
db_conn.close()
db_conn = get_db_connection()
# 跳过当前任务,继续下一个
continue
else:
# 其他操作异常,按需处理
raise
except Exception as e:
# 外层异常处理,确保连接重置
if db_conn:
db_conn.rollback()
db_conn.close()
db_conn = get_db_connection()
优化连接超时参数
连接时设置timeout参数(单位:秒),让SQLite自动等待其他进程释放锁,默认超时为5秒,可根据业务调整为30-60秒,减少锁异常触发频率:sqlite3.connect("path to database", timeout=30)使用行级锁避免冲突(SQLite 3.35.0+)
查询任务时使用SELECT ... FOR UPDATE SKIP LOCKED,直接跳过已被其他进程锁定的行,从根源避免多进程抢同一任务导致的死锁:SELECT id, task FROM tasks WHERE status = 'pending' LIMIT 1 FOR UPDATE SKIP LOCKED此语法要求SQLite版本≥3.35.0,确保你的环境满足版本要求。
进程独立连接
多进程环境下,绝对不能共享SQLite连接,每个进程必须创建独立的连接实例。SQLite的连接对象不支持跨进程共享,共享会导致不可预测的锁问题和数据损坏。避免长事务
尽量缩短事务时长,执行完更新后立即提交,不要让事务长时间处于未提交状态,减少锁持有时间。
内容的提问来源于stack exchange,提问作者Dmitry Kuzminov

