You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:09:21