当这个错误发生时,有没有一种方法可以记录哪个其他进程ID负责锁定?
不,当异常发生时不会记录该信息。在SQLite内部尝试获取互斥锁和文件锁时(默认为5分钟),通常会引发“OperationalError: database is locked”异常,此时SQLite返回“SQLITE_BUSY”,但“SQLITE_BUSY”也可能在其他点上报告。 SQLite错误代码不带任何进一步的上下文,例如持有锁的另一个进程的PID,并且可以想象,在当前进程放弃获取它之前,锁已在两个其他进程之间传递!
最好的方法是使用“lsof <数据库文件名>”枚举当前访问文件的进程,但这并不能让您更接近找出哪一个实际上需要很长时间才能提交。
相反,我建议您使用显式事务和详细日志记录来仪表化您的代码,以便在遇到“OperationalError”异常时,您可以检查日志以了解在该时间窗口内发生了什么。
一个可用于此的Python上下文管理器是:
import logging
import sys
import time
import threading
from contextlib import contextmanager
from uuid import uuid4
logger = logging.getLogger(__name__)
@contextmanager
def logged_transaction(con, stack_info=False, level=logging.DEBUG):
"""Manage a transaction and log start and end times.
Logged messages include a UUID transaction ID for ease of analysis.
If trace is set to True, also log all statements executed.
If stack_info is set to True, a stack trace is included to record
where the transaction was started (the last two lines will point to this
context manager).
"""
transaction_id = uuid4()
thread_id = threading.get_ident()
def _trace_callback(statement):
logger.log(level, '(txid %s) executing %s', transaction_id, statement)
if trace:
con.set_trace_callback(_trace_callback)
logger.log(level, '(txid %s) starting transaction', transaction_id, stack_info=stack_info)
start = time.time()
try:
with con:
yield con
finally:
exc_info = sys.exc_info()
if exc_info[0] is None:
exc_info = None
if trace:
con.set_trace_callback(None)
logger.log(level, '(txid %s) transaction closed after %.6f seconds', transaction_id, time.time() - start, exc_info=exc_info)
上述内容将创建起始和结束条目,如果有异常信息则包括在内,可选择跟踪连接上执行的所有语句,并可以包括一个堆栈跟踪,告诉您上下文管理器使用的位置。确保在格式化日志消息时
包含日期和时间,以便跟踪事务启动时间。
我会在使用连接的任何代码周围使用它,这样您也可以计时选择操作:
with logged_transaction(connection):
cursor = connection.cursor()
也许仅使用这个上下文管理器就可以解决您的问题,此时您需要分析为什么没有这个上下文管理器的代码会留下未提交的事务。
您可能还想在sqlite3.connect()
调用中使用较低的timeout
值以加快进程速度;您可能不必等待完整的5分钟来检测情况。
关于线程的注意事项:启用跟踪时,假定您为不同的线程使用单独的连接。如果不是这种情况,则需要永久注册跟踪回调,然后解决当前线程要使用哪个事务 ID 的问题。
sqlite3.connect()
调用的默认timeout
值)。 - Martijn Pieterslsof
,您可以使用它来查找打开该文件的每个进程,并查看哪个进程有锁定 - 可能是正在进行 select 操作的那个进程。如果您希望数据库读取器不会阻塞数据库写入器,反之亦然,请查看 WAL 日志模式。 - Shawn