Базовый параллельный писатель SQLite в Python - PullRequest
0 голосов
/ 23 октября 2018

Я создал очень простой скрипт, который периодически записывает некоторые данные в базу данных:

test.py

import sqlite3
import sys
import time

DB_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS items (item TEXT)'
DB_INSERT = 'INSERT INTO items VALUES (?)'
FILENAME = 'test.db'


def main():
    index = int()
    c = sqlite3.connect(FILENAME)
    c.execute(DB_CREATE_TABLE)
    c.commit()

    while True:
        item = '{name}_{index}'.format(name=sys.argv[1], index=index)
        c.execute(DB_INSERT, (item,))
        c.commit()
        time.sleep(1)
        index += 1

    c.close()


if __name__ == '__main__':
    main()

Теперь я могу достичь простого параллелизмазапустив сценарий несколько раз:

python3 test.py foo &
python3 test.py bar &

Я пытался прочитать несколько статей о сценариях, записывающих в один и тот же файл базы данных одновременно, но все же я не уверен, как мой сценарий будет обрабатывать такое событие, и яЯ никак не мог понять, как это проверить.

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

Соответствует ли моя текущая реализация моим ожиданиям?Если это не так, как он ведет себя в случае такого события и как я могу это исправить?

1 Ответ

0 голосов
/ 23 октября 2018

TL; DR

Этот сценарий будет соответствовать ожиданиям.

Объяснение

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

Точнее, второй экземпляр сценарияждет в течение 5 секунд (по умолчанию), а затем вызывает OperationalError с сообщением database is locked.Как заметил @roganjosh, это поведение на самом деле специфично для оболочки Python SQLite.В документации говорится :

Когда к базе данных получают доступ несколько соединений, и один из процессов изменяет базу данных, база данных SQLite блокируется до тех пор, пока эта транзакция не будет зафиксирована.Параметр timeout указывает, как долго соединение должно ожидать, пока блокировка не исчезнет, ​​до возникновения исключения.Значение по умолчанию для параметра времени ожидания - 5,0 (пять секунд).

Тесты

Чтобы продемонстрировать событие столкновения двух экземпляров, я изменил функцию main:

def main():
    c = sqlite3.connect(FILENAME)
    c.execute(DB_CREATE_TABLE)
    c.commit()
    print('{} {}: {}'.format(time.time(), sys.argv[1], 'trying to insert ...'))

    try:
        c.execute(DB_INSERT, (sys.argv[1],))
    except sqlite3.OperationalError as e:
        print('{} {}: {}'.format(time.time(), sys.argv[1], e))
        return

    time.sleep(int(sys.argv[2]))
    c.commit()
    print('{} {}: {}'.format(time.time(), sys.argv[1], 'done')) 
    c.close()

В документации говорится, что база данных заблокирована, пока транзакция не будет зафиксирована.Так что простого сна во время транзакции должно быть достаточно для ее проверки.

Тест 1

Мы запускаем следующую команду:

python3 test.py first 10 & sleep 1 && python3 test.py second 0

Первый экземпляр запускается и после1с запускается второй экземпляр.Первый экземпляр создает транзакцию длиной 10 с, во время которой второй пытается выполнить запись в базу данных, ожидает и затем вызывает исключение.Журнал демонстрирует, что:

1540307088.6203635 first: trying to insert ...
1540307089.6155508 second: trying to insert ...
1540307094.6333485 second: database is locked
1540307098.6353421 first: done

Тест 2

Мы выполняем следующую команду:

python3 test.py first 3 & sleep 1 && python3 test.py second 0

Первый экземпляр запускается, а через 1 секунду второй экземплярбыть запущенным.Первый экземпляр создает транзакцию длиной 3 с, во время которой второй пытается выполнить запись в базу данных и ожидает.Поскольку он был создан после 1 с, он должен ждать 3 с - 1 с = 2 с, что меньше 5 с по умолчанию, поэтому обе транзакции завершатся успешно.Журнал демонстрирует, что:

1540307132.2834115 first: trying to insert ...
1540307133.2811155 second: trying to insert ...
1540307135.2912169 first: done
1540307135.3217440 second: done

Заключение

Время, необходимое для завершения транзакции, значительно меньше (миллисекунды), чем ограничение времени блокировки (5 с), поэтому в этом сценарии сценарий действительносоответствует ожиданиям.Но как @HarlyH.прокомментировал, что транзакции ждут в очереди для принятия, поэтому для интенсивно используемой или очень большой базы данных это не хорошее решение, так как связь с базой данных станет медленной.

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