Есть ли способ, когда эта ошибка происходит, регистрировать, какой другой идентификатор процесса отвечает за блокировку?
Нет, эта информация не записывается при возникновении исключения. Исключение OperationalError: database is locked
обычно возникает после тайм-аута (по умолчанию 5 минут) при попытке получить мьютекс и блокировку файла во внутренних органах SQLite, после чего SQLite возвращает SQLITE_BUSY
, но SQLITE_BUSY
также может быть сообщается в других точках. Коды ошибок SQLite не несут никакого дальнейшего контекста, такого как PID другого процесса, который удерживал блокировку, и вполне возможно, что блокировка была передана между двумя другими процессами, прежде чем текущий процесс прекратил попытки получить ее!
В лучшем случае вы можете перечислить, какие процессы в данный момент обращаются к файлу, используя lsof <filename of database>
, но это не приблизит вас к выяснению, какой из них на самом деле занимает слишком много времени для фиксации.
Вместо этого я бы снабдил ваш код явными транзакциями и подробной регистрацией того, когда вы начинаете и делаете транзакции. Затем, когда вы столкнетесь с 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:
# record exception information, if an exception is active
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()
# ...
Возможно, только использование этого диспетчера контекста делает вашу проблему исчезающей, и в этот момент вам нужно будет проанализировать, почему код без этого диспетчера контекста оставляет открытую транзакцию без фиксации.
Вы также можете использовать меньшее значение timeout
в вызовах sqlite3.connect()
, чтобы ускорить процесс; Вам, возможно, не придется ждать целых 5 минут, чтобы обнаружить ситуацию.
Примечание о потоке: при включении трассировки предполагается, что вы используете отдельные соединения для отдельных потоков. Если это не так, вам необходимо постоянно зарегистрировать обратный вызов трассировки, который затем определит, какой идентификатор транзакции использовать для текущего потока.