Я пытаюсь отследить ошибку в моей python
программе, когда два процесса обращаются к базе данных sqlite
.
- Процесс 1 периодически читает
- Процесс 2 периодически вставляется
Если по какой-либо причине вставка не удалась, я разработал приложение python
, чтобы добавить неудачную вставку в буфер и повторить попытку при следующей попытке вставки с помощью executemany
.
Недавно я обнаружил, что в базе данных есть повторяющихся записей .
Я могу воспроизвести поведение с помощью следующего кода. Код работает на очень медленном оборудовании, поэтому я фальсифицировал долгие запросы и уменьшил время ожидания блокировки.
process1.py
import sqlite3
import time
con = sqlite3.connect("data.db", timeout=0.1)
con.executescript("CREATE TABLE IF NOT EXISTS data (counter int NOT NULL);")
con.create_function("sleep", 1, time.sleep)
while True:
try:
with con:
con.execute("SELECT counter, sleep(1) FROM data LIMIT 1;")
except sqlite3.OperationalError as e:
print("Reading failed. Ex: {}".format(e))
time.sleep(0.25)
process2.py
import sqlite3
import time
con = sqlite3.connect("data.db", timeout=0.1)
counter = 0
while True:
try:
with con:
con.executemany("INSERT INTO data (counter) VALUES (?);", [(counter,)])
except sqlite3.OperationalError as e:
# con.rollback() # possible workaround?
print("Writing failed at '{}'. Ex: {}".format(counter, e))
counter += 1
time.sleep(0.25)
Когда вы запускаете код в двух отдельных терминалах для имитации описанной выше проблемы, вы можете увидеть, что Процесс 2 выдает OperationalError
с сообщением база данных заблокирована . Все идет нормально.
Странная вещь в том, что когда вы запрашиваете базу данных, казалось бы, неудачные вставки все равно есть. Это по замыслу?
Поведение меняется при переключении Процесс 1 с чтения на запись. Теперь неудачные вставки из Процесс 2 не будут отображаться в таблице.
process1_extended.py
import sqlite3
import time
con = sqlite3.connect("data.db", timeout=0.1)
con.create_function("sleep", 1, lambda x: time.sleep(1) or x)
con.executescript("CREATE TABLE IF NOT EXISTS data (counter int NOT NULL);")
counter = 1000000 # distinguishes between the two writing processes
while True:
try:
with con:
con.executemany("INSERT INTO data (counter) SELECT sleep(?)", [(counter,)])
except sqlite3.OperationalError as e:
print("Writing2 failed. Ex: {}".format(e))
counter += 1
time.sleep(0.25)
Мне известно, что операция INSERT
/ DML
отличается от операции SELECT
и связана с различными видами блокировок (общая блокировка против эксклюзивной блокировки), но что я не могу объяснить себе, так это два разных результата. Как я могу предотвратить вставку данных sqlite
при возникновении исключения?
con.rollback()
в блоке исключений Процесс 2 кажется обходным решением, но я не уверен, подразумевает ли это другие предостережения. И разве это не должно применяться автоматически диспетчером контекста ?
Дополнение к ответу:
Исключение в Процесс 2 , если Процесс 1 читает, происходит при фиксации в менеджерах контекста __exit__
из-за общей блокировки. Если , другая блокировка находится в ожидании, выполняется откат, поскольку ранее возникла исключительная ситуация, а метод __exit__
вызывается с аргументами exc_type/exc_value/exc_tb
set.