Сбой SQLite INSERT с исключением, но в базе данных появляется запись - PullRequest
0 голосов
/ 11 сентября 2018

Я пытаюсь отследить ошибку в моей 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.

1 Ответ

0 голосов
/ 11 сентября 2018

Вероятно, это разница между замками (SHARED v. RESERVED).From SQLite Doc ):

Попытка выполнить COMMIT может также привести к коду возврата SQLITE_BUSY, если другой поток или процесс имеет общую блокировку базы данных, которая помешала работе базы данных.от обновления.Когда COMMIT терпит неудачу таким образом, транзакция остается активной, и COMMIT можно повторить позже, после того как считыватель сможет очистить.

В примере чтения это блокировка SHARED, транзакция остается активной и получает COMMIT после следующей успешной INSERT.Я знаю, что API doc говорит, что менеджер контекста выполнит откат.Но я также знаю, что вижу.

Объект соединения имеет атрибут in_transaction:

True, если транзакция активна (есть незафиксированные изменения), False иначе.Атрибут только для чтения.

Я добавил print s к своему репро и обнаружил, что на самом деле транзакции оставались активными , когда вставка не удалась.

before insert  0  in tx?  False
after insert  0  in tx?  False
before insert  1  in tx?  False
Writing failed at '1'. Ex: database is locked  in tx?  True
before insert  2  in tx?  True
Writing failed at '2'. Ex: database is locked  in tx?  True
before insert  3  in tx?  True
Writing failed at '3'. Ex: database is locked  in tx?  True
before insert  4  in tx?  True
after insert  4  in tx?  False
before insert  5  in tx?  False
Writing failed at '5'. Ex: database is locked  in tx?  True
before insert  6  in tx?  True
Writing failed at '6'. Ex: database is locked  in tx?  True
before insert  7  in tx?  True
Writing failed at '7'. Ex: database is locked  in tx?  True
before insert  8  in tx?  True
after insert  8  in tx?  False
before insert  9  in tx?  False
Writing failed at '9'. Ex: database is locked  in tx?  True
before insert  10  in tx?  True
Writing failed at '10'. Ex: database is locked  in tx?  True
before insert  11  in tx?  True

Все строки были вставлены.Явный откат предотвратит эту проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...