Russian Qt Forum

Qt => Базы данных => Тема начата: vregess от Апрель 02, 2015, 21:18



Название: 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 вытягиваем все строки, то блокировки не будет:
Код
C++ (Qt)
   m_model->select();
   while(m_model->canFetchMore())
         m_model->fetchMore();
 

но в некоторых случаях загрузка всех данных не желательна.

Как победить? Наследоваться от 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, т.к. соединение можно использовать только в пределах родительского потока.

но в некоторых случаях загрузка всех данных не желательна.

Приведите пример такого случая. Не могу придумать.
1 млн строк в БД. Ну как пример (у меня не так).
Да и не зря же придумали 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:
Код
C++ (Qt)
QVERIFY( !query2.exec("BEGIN IMMEDIATE TRANSACTION") );
 

транзакция не начинается, но должна начинаться.

вложения к последнему посту не смотрел, но почему бы не отдать моделям соединения на чтения - пусть себе читают сколько хотят, а если надо записать - делать это по другому новому соединению ?

Интересная идея, но придется переписывать 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 и блокировок.