Ошибка QSQLite: база данных заблокирована
Я новичок в разработке Qt, как он обрабатывает потоки (сигналы и слоты) и базы данных (и SQLite в этом). Уже 4 недели я начал работать над упомянутыми технологиями. Это первый раз, когда я пишу вопрос о SO, и я чувствую, что провел исследование, прежде чем прийти ко всем вам. Это может показаться немного длинным и, возможно, дубликатом, но я прошу всех вас прочитать его один раз, прежде чем отклонить как дубликат или т. Д.
Контекст:
Я работаю над приложением Windows, которое выполняет определенную операцию X с базой данных. Приложение разработано в Qt и использует QSQLite в качестве движка базы данных. Это однопоточное приложение, то есть таблицы обрабатываются последовательно. Однако по мере увеличения размера БД (по количеству таблиц и записей) эта обработка замедляется. Результат этой операции X записывается в отдельную таблицу результатов в той же БД. Выполняемая обработка несущественна для проблемы, но в общих чертах вот что она делает:
Читать строку из Table_X_1
Прочитать строку из Table_X_2
Выполните некоторые операции со строками (только чтение)
Перенесите результаты в таблицу Table_X_Results (это единственная запись, выполняемая в БД)
Table_X_1 и Table_X_2 идентичны по количеству и типам столбцов и количеству строк, только данные могут отличаться.
Что я пытаюсь сделать:
Чтобы повысить производительность, я стараюсь сделать приложение многопоточным. Первоначально я порождаю два потока (используя QtConcurrentRun). Две таблицы можно разделить на два типа, скажем, A и B. Каждый поток позаботится о таблицах двух типов. Обработка внутри потоков остается неизменной, то есть внутри каждого потока таблицы обрабатываются последовательно.
Функция такова, что она использует SELECT для выборки строк для обработки и INSERT для вставки результата в таблицу результатов. Для вставки результатов я использую транзакции.
Я создаю все промежуточные таблицы, таблицы результатов и индексы перед тем, как начать работу. Я открываю и закрываю связи каждый раз. Для потоков я создаю и открываю соединение перед входом в цикл (по одному для каждого потока).
ПРОБЛЕМА:
Внутри моей функции обработки я получаю следующую (неприятную, позорную, упрямую) ошибку:
QSqlError (5, "Невозможно получить строку", "база данных заблокирована")
Я получаю эту ошибку, когда пытаюсь прочитать строку из БД (используя SELECT). Это в той же функции, в которой я выполняю свои ВСТАВКИ в таблицу результатов. SELECT и INSERT находятся в одной транзакции (пара начала и фиксации). Для INSERT я использую подготовленный оператор (SQLiteStatement).
Причины, казалось бы, своеобразных вещей, которые я делаю:
- Я использую QtConcurrentRun для создания потоков, потому что это легко сделать! Я пытался использовать QThread (не подкласс QThread, но другой метод). Это также приводит к той же проблеме.
- Я компилирую с DSQLITE_THREADSAFE=0, чтобы избежать сбоя приложения. Если я использую значение по умолчанию (DSQLITE_THREADSAFE=1), мое приложение вылетает при SQLiteStatement::recordSet->Reset(). Кроме того, с параметром по умолчанию вступает в действие внутренний механизм синхронизации SQLITE, который может быть ненадежным. Если понадобится, я использую явную синхронизацию.
- Делать приложение многопоточным для повышения производительности, а не делать этого. Я забочусь обо всех оптимизациях, рекомендованных там.
- Использование QSqlDatabase::setConnectOptions с QSQLITE_BUSY_TIMEOUT=0. Ссылка предположила, что это предотвратит немедленную блокировку БД и, следовательно, может дать моему потоку (-ам) подходящее количество времени для "мирной смерти". Это не удалось: БД блокировалась гораздо чаще, чем раньше.
Наблюдения:
- База данных блокируется только и сразу после возврата одного из потоков. Такое поведение соответствует.
- При компиляции с DSQLITE_THREADSAFE = 1 приложение завершает работу при возврате одного из потоков. Вызовите точки стека в SQLiteStatement:: recordSet-> Reset () в моей функции и в winMutexEnter() (вызывается из EnterCriticalSection()) в sqlite3.c. Это также соответствует.
- Потоки, созданные с помощью QtConcurrentRun, не умирают сразу.
- Если я использую QThreads, я не могу заставить их вернуться. То есть я чувствую, что поток никогда не возвращается, даже если я правильно соединил сигналы и слоты. Как правильно ждать потоков и как долго они умирают?
- Поток, завершающий выполнение, никогда не возвращается, он заблокировал БД и, следовательно, ошибку.
- Я проверил SQLITE_BUSY и попытался заставить поток спать, но не смог заставить его работать. Как правильно спать в Qt (для потоков, созданных с помощью QtConcurrentRun или QThreads)?
Когда я закрываю свои соединения, я получаю это предупреждение:
QSqlDatabasePrivate:: removeDatabase: соединение 'DB_CONN_CREATE_RESULTS' все еще используется, все запросы перестанут работать.
Это имеет какое-то значение? В некоторых ссылках указывалось, что это предупреждение возникает из-за использования локальной QSqlDatabase и не будет возникать, если соединение сделано членом класса. Однако может ли это быть причиной моей проблемы?
Дальнейшие эксперименты:
- Я думаю о создании другой базы данных, которая будет содержать только таблицу результатов (Table_X_Results). Смысл в том, что хотя потоки будут читать из одной БД (той, что у меня есть в настоящее время), они получат возможность записи в другую БД. Тем не менее, я все еще могу столкнуться с той же проблемой. Более того, я прочитал на форумах и вики о том, что возможно иметь два потока, читающих и пишущих в одной и той же БД. Так почему я не могу заставить этот сценарий работать?
- В настоящее время я использую SQLITE версии 3.6.17. Может ли это быть проблема? Будет ли лучше, если я буду использовать версию 3.8.5?
Я пытался опубликовать веб-ресурсы, которые я уже исследовал, но я получаю сообщение: "Мне нужно 10 повторений, чтобы опубликовать более 2 ссылок". Любая помощь / предложения будут высоко ценится.