Название: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 02, 2015, 21:18 Очередной вопрос про sqlite и потоки.
Т.к. с embedded firebird есть проблемы (http://www.prog.org.ru/topic_28195_0.html), решил откатиться на sqlite. Но тут свои грабли. Основная проблема (imho) в том, что QSqlTableModel блокирует базу, потому что использует подгрузку строк (fetchMore) и все время держит активным QSqlQuery. Поэтому я включил WAL (http://sqlite.org/wal.html), который по идее должен решать проблему блокировки читателей и писателей, и использую IMMEDIATE TRANSACTION, которую рекомендуют для многопоточных приложений, и в дополнение это решает проблему синхронизаций между несколькими подключениями (http://sqlite.org/isolation.html). Так вот, нифига это не работает, прошу помощь зала. Накатал простой пример: 1. Есть QSqlTableModel и 2 кнопки. 2. Нажимаем кнопку 1 - worker в отдельном потоке заполняет таблицу, используя транзакцию (нажимаем 2 раза). 3. пинаем QSqlTableModel, чтобы она обновила данные. 4. Нажимаем кнопку 2 - в главном потоке пытаемся записать что-то в базу, тоже в транзакции. 5. Ничего не получается - база заблокирована. Если на шаге 3 вытягиваем все строки, то блокировки не будет: Код
но в некоторых случаях загрузка всех данных не желательна. Как победить? Наследоваться от QSqlQueryModel/QSqlTableModel и переопределять подгрузку данных? Или можно как-то настроить sqlite? И почему WAL здесь не решает проблему с блокировками? PS qt 5.4.0, msvc 2010 x32, win7. Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 03, 2015, 13:02 насколько я помню, полностью многопоточной sqlite и не является - она не упадет при обращении из другого потока, но вернет SQLITE_BUSY
Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: Termit от Апрель 03, 2015, 16:09 но в некоторых случаях загрузка всех данных не желательна. Приведите пример такого случая. Не могу придумать. Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 03, 2015, 21:47 насколько я помню, полностью многопоточной sqlite и не является - она не упадет при обращении из другого потока, но вернет SQLITE_BUSY Не совсем так. Есть несколько режимов многопоточной работы: Multi-thread - каждый поток должен использовать свое подключение. Serialized - одно подключение можно использовать в любом потоке. Это режим с наименьшими ограничениями для хост-приложения, но неприемлемый для Qt, т.к. соединение можно использовать только в пределах родительского потока. но в некоторых случаях загрузка всех данных не желательна. Приведите пример такого случая. Не могу придумать. Да и не зря же придумали QAbstractItemModel::fetchMore() и QAbstractItemModel::canFetchMore(). Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: Termit от Апрель 04, 2015, 12:47 1 млн строк в БД. Ну как пример (у меня не так). Да и не зря же придумали QAbstractItemModel::fetchMore() и QAbstractItemModel::canFetchMore(). Ну да и не зря же придумали пагинацию, фильтры и т.д. Не представляю реальное приложение где пользователю покажут таблицу в 1 млн строк сиди мол читай, разбирайся... Воспользуйтесь QSqlQueryModel установите лимиты, фильтры и выгребайте все через fetchMore. ИМХО при работе со sqlite и моделями это единственное правильное решение. Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 04, 2015, 13:31 Воспользуйтесь QSqlQueryModel установите лимиты, фильтры и выгребайте все через fetchMore. QSqlQueryModel и выгребает частями, это видно по примеру (также там видно, что sqlite не знает сколько данных всего придет - скролл бар прыгает) Как я понимаю проблема тут в другом - пока QSqlQueryModel не выгреб еще все данные - база в статусе SHARED https://www.sqlite.org/lockingv3.html И никакая запись не пройдет (не важно из какого потока), что конечно неудобно Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 04, 2015, 15:08 получилось поменять пример чтобы все работало - надо при запросе insert в on_btt2_clicked() дать _новое_ имя для соединения (т.е. не подавать по соединению, которое использует модель для отображения)
Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 06, 2015, 14:31 Как я понимаю проблема тут в другом - пока QSqlQueryModel не выгреб еще все данные - база в статусе SHARED https://www.sqlite.org/lockingv3.html https://www.sqlite.org/lockingv3.html говорит: Цитировать The document only describes locking for the older rollback-mode transaction mechanism. Locking for the newer write-ahead log or WAL mode is described separately. Я использую WAL. И никакая запись не пройдет (не важно из какого потока), что конечно неудобно Как раз с использованием WAL все будет работать, т.к. для этого он и предназначен - чтобы "читатели" не мешали "писателям".Написал тест, где запись проходит при активном читающем запросе. Но sqlite блокирует базу для соединения и в режиме WAL в определенном случае, и что-то в доках по этому поводу в https://www.sqlite.org/wal.html ничего не нашел. Прикладываю тесты, кому интересно (все тесты для режима WAL): 1. testOneThread - все в одном потоке. Сначала делаем запрос и оставляем его активным, а следом в другом запросе пытаемся изменить БД. 2. testLockedMultiThread - делаем запрос в главном потоке и оставляем его активным, изменяем БД в другом потоке, а потом опять в главном потоке пытаемся изменить БД. Вот этот тест аналог программки в первом сообщении. Тут происходит блокировка. 3. testUnLockedMultiThread - аналог testLockedMultiThread, но первый запрос делаем неактивным. Все работает, ничего не блокируется. testLockedMultiThread - не совсем понятно, почему так происходит. Пока мысль такая: внутри отдельного соединения все работает, как описано в https://www.sqlite.org/lockingv3.html, а взаимоотношения между несколькими соединениями описываются в https://www.sqlite.org/wal.html. Видимо поэтому не подавать по соединению, которое использует модель для отображения и работает.не зря же придумали пагинацию, фильтры и т.д. Все же подгрузка данных и загрузка их по частям это разные вещи. Да, цель одна, но подходы разные.ИМХО при работе со sqlite и моделями это единственное правильное решение. Похоже на то. Я бы сказал, что не стоит держать активных соединений. В общем пока решил написать свою модель, которая будет подгружать данные не активным запросом, как в Qt, а отдельными запросами с LIMIT. Вопрос все еще открыт - почему падает testLockedMultiThread и где об этом написано? Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 07, 2015, 12:44 вложения к последнему посту не смотрел, но почему бы не отдать моделям соединения на чтения - пусть себе читают сколько хотят, а если надо записать - делать это по другому новому соединению ?
Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 07, 2015, 12:49 ********* Start testing of LockTest *********
Config: Using QtTest library 5.4.1, Qt 5.4.1 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 4.8.1 20130909 [gcc-4_8-branch revision 202388]) PASS : LockTest::initTestCase() PASS : LockTest::testOneThread() PASS : LockTest::testLockedMultiThread() PASS : LockTest::testUnLockedMultiThread() PASS : LockTest::cleanupTestCase() Totals: 5 passed, 0 failed, 0 skipped, 0 blacklisted ********* Finished testing of LockTest ********* что не так ? Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 07, 2015, 12:54 Все так.
строка 235: Код
транзакция не начинается, но должна начинаться. вложения к последнему посту не смотрел, но почему бы не отдать моделям соединения на чтения - пусть себе читают сколько хотят, а если надо записать - делать это по другому новому соединению ? Интересная идея, но придется переписывать QSqlTableModel в тех местах, где происходит запись в бд. Хотя так и так что-то надо переписывать, возможно это самый короткий путь. Я попробую. Спасибо за советы. Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 07, 2015, 14:25 вложения к последнему посту не смотрел, но почему бы не отдать моделям соединения на чтения - пусть себе читают сколько хотят, а если надо записать - делать это по другому новому соединению ? Все же так просто не получается. Основные изменения базы идут через модели QSqlTableModel. Если даже сделать запись через отдельное соединение, переопределив некоторые методы, то получим проблему неполных данных в читающем соединении, и кучу сопутствующих проблем. Пример. Чтобы добавить строку переопределяем QSqlTableModel::deleteRowFromTable() и там, создав транзакцию в новом соединении, делаем инсерт. Чтобы модель узнала об этом, надо заново делать select(), но вряд ли так можно делать из deleteRowFromTable(). Не буду расписывать почему. Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 08, 2015, 08:35 Пример. Чтобы добавить строку переопределяем QSqlTableModel::deleteRowFromTable() и там, создав транзакцию в новом соединении, делаем инсерт. Чтобы модель узнала об этом, надо заново делать select(), но вряд ли так можно делать из deleteRowFromTable(). Не буду расписывать почему. QTimer::singleShot(100, myModel, SLOT(select())); Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 08, 2015, 09:34 QTimer::singleShot(100, myModel, SLOT(select())); Не, ну это костыль, и не факт, что все будет гладко. В общем я сделал аналог QSqlQueryModel, чтобы она делала fetchMore() не активным запросом, а вытаскивала по кускам при помощи LIMIT/OFFSET, и его наследника (аналог QSqlTableModel), который все изменения делает в IMMEDIATE TRANSACTION. Пока тестирую, но должно заработать. Вопрос "Как победить?" вроде решился, но так и не ясно почему WAL не решает проблему с блокировками? Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: qate от Апрель 08, 2015, 13:23 В общем я сделал аналог QSqlQueryModel, чтобы она делала fetchMore() не активным запросом, а вытаскивала по кускам при помощи LIMIT/OFFSET, и его наследника (аналог QSqlTableModel), который все изменения делает в IMMEDIATE TRANSACTION. Пока тестирую, но должно заработать. каждый LIMIT/OFFSET это же новый запрос, это медленнее чем курсором каков ожидаемый объем таблиц ? Вопрос "Как победить?" вроде решился, но так и не ясно почему WAL не решает проблему с блокировками? копать глубже, писать тесты с использованием https://www.sqlite.org/capi3ref.html Название: Re: sqlite + QSqlTableModel в многопоточном приложении Отправлено: vregess от Апрель 08, 2015, 13:34 каждый LIMIT/OFFSET это же новый запрос, это медленнее чем курсором каков ожидаемый объем таблиц ? Медленнее, но из-за особенностей sqlite с курсором вообще работать не будет, так что без вариантов: если хочешь писать/читать, никому не мешая, из разных потоков, то нельзя держать активные запросы. Большинство таблиц небольшие, но возможна ситуация, когда лучше все сразу не загружать. Наверное вопрос можно закрыть, если только кто-то не решиться провести более глубокое расследование по поводу WAL и блокировок. |