Python SQLite3多进程写入同库报database is locked问题咨询
SQLite并发写锁问题解决方案
锁机制核心结论
先明确三个锁相关的核心问题:
- 创建数据库连接不会加任何锁。连接初始化仅建立文件访问句柄、加载驱动配置,不会触发读写锁申请,在重试循环里反复创建/销毁连接只会产生不必要的IO开销,不仅解决不了锁冲突,还会拉高文件系统负载。
- 执行
cur.execute()触发写操作(INSERT/UPDATE/DELETE等)时,SQLite会立即尝试申请预留锁(RESERVED LOCK),如果此时其他连接已经持有排他写锁,就会进入锁等待,并非只有con.commit()才会加锁。 con.commit()执行时,连接会将持有的预留锁升级为排他锁(EXCLUSIVE LOCK),该阶段需要所有其他持有读锁的连接释放锁才能完成升级,是锁冲突最高发的环节。con.rollback()本身用于释放当前事务持有的锁,不会主动申请新锁,但如果回滚前前序操作已经持有锁,回滚过程出现IO异常也可能抛出锁相关错误。
当前使用的无退避、无次数上限的无限重试逻辑存在明显缺陷:8-10个容器并发写时很容易触发活锁——所有连接同时空转抢锁,既占满CPU资源,又会让设置的timeout=20参数完全失效,最终出现进程卡死的情况。
优化实现方案
1. 连接配置优化
首先调整连接初始化参数,从底层降低锁冲突概率:
import sqlite3 con = sqlite3.connect( database="database.db", timeout=20, isolation_level=None # 关闭隐式自动事务,手动控制事务边界 ) # 核心优化:开启WAL日志模式,读写互不阻塞,并发性能较默认DELETE模式提升数倍 con.execute("PRAGMA journal_mode=WAL;") # 显式设置锁等待超时,兼容部分pysqlite版本connect timeout不生效的问题 con.execute("PRAGMA busy_timeout=20000;") # 调整同步级别为NORMAL,平衡数据安全和写入性能,已提交数据不会丢失 con.execute("PRAGMA synchronous=NORMAL;") cur = con.cursor()
WAL模式下,读操作不会阻塞写,写操作也不会阻塞读,8-10个并发写的场景不需要额外架构调整就能稳定运行。
2. 替换无限重试逻辑
不要对单个execute/commit/rollback单独做重试,要把整个事务包裹在重试逻辑中,加上指数退避、随机抖动和重试次数上限,从根本上避免活锁和无限卡死:
import time import random MAX_RETRIES = 10 BASE_RETRY_DELAY = 0.1 # 初始重试间隔100ms def write_with_retry(cur, write_sql, params=None): for attempt in range(MAX_RETRIES): try: # 事务开始直接申请预留锁,避免执行到commit阶段才发现锁冲突 cur.execute("BEGIN IMMEDIATE;") if params: cur.execute(write_sql, params) else: cur.execute(write_sql) cur.connection.commit() return except sqlite3.OperationalError as e: # 非锁类错误直接抛出,不做重试 if "database is locked" not in str(e): cur.connection.rollback() raise # 指数退避+随机抖动,打散重试时间点,避免多连接同时抢锁 delay = BASE_RETRY_DELAY * (2 ** attempt) + random.uniform(0, 0.1) time.sleep(delay) cur.connection.rollback() # 重试次数耗尽后主动抛出异常,避免无限卡死 raise RuntimeError("Database persistently locked, write failed after maximum retries")
3. 避坑说明
- 不要在重试循环内反复创建连接和游标:长连接复用是正确实践,反复建连只会徒增开销,对解决锁冲突没有任何帮助。
- 不要拉长事务周期:拿到锁之后尽快完成写操作并提交,事务中间不要插入网络请求、休眠等耗时逻辑,持锁时间越短,锁冲突概率越低。
- 不要在单事务内堆积过多写操作:单事务批量写入量控制在100条以内,事务过大会导致持锁时间过长,大幅提升其他连接的锁等待超时概率。
如果后续写并发涨到20个容器以上,可以额外加一层极薄的写代理,所有写请求统一经过代理单线程写入数据库,读请求仍然由各容器直接访问即可,WAL模式下读请求完全不会产生冲突。
内容的提问来源于stack exchange,提问作者SajanGohil
相关产品推荐
相关产品推荐

