Виталий Антипов
Виталий АнтиповҚаң. 26, 2018, 4:34 Т.Ж.

"database is locked Невозможно получить строку"

Добрый день! При многопользовательском использовании sqlite3, первая программа, создавшая соединение, имеет все права, остальные могут только читать базу. Любая попытка создания, редактирования, удаления приводят к примерно такому:

error delete row  Baza
"database is locked Невозможно получить строку"
Подскажите, как безопасно решить эту проблему?
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

18
Evgenii Legotckoi
  • Қаң. 26, 2018, 4:48 Т.Ж.
  • (өңделген)

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

SQLite не предназначен для многопользовательских систем сам по себе.

Либо лучшим решением будет использовать MySQL или PostgreSQL. Рекомендую PostgreSQL.
    Виталий Антипов
    • Қаң. 26, 2018, 5:08 Т.Ж.

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

      Evgenii Legotckoi
      • Қаң. 26, 2018, 5:13 Т.Ж.
      • (өңделген)
      • Жауап шешім ретінде белгіленді.

      Как вариант, можно попробовать работать с транзакциями, то есть открывать транзакции и закрывать их при каждой записи.

      QSqlDatabase db = QSqlDatabase::database();
      db.transaction();
       
      QSqlQuery q;
      qDebug() << q.exec("SELECT;"); 
       
      q.clear();
      if(!db.commit()){
      db.rollback();
      }
      В общем-то для такого они и предназначены, но мне как-то не было необходимости настолько вручную управлять данным процессом.
      Проверьте, может поможет решить вашу проблему. Но по факту это один из правильных путей.
        Виталий Антипов
        • Қаң. 26, 2018, 5:15 Т.Ж.

        Спасибо, попробую.

          Виталий Антипов
          • Қаң. 26, 2018, 7:40 Т.Ж.

          Сделал вот так:

          bool DataBase::insertIntoTable(const QVariantList &data)
          {
              QSqlDatabase db = QSqlDatabase::database();
              db.transaction();
              QSqlQuery querys;
              querys.prepare("INSERT INTO ... ");
              if(!querys.exec()){
                  qDebug() << "error insert into ";
                  qDebug() << querys.lastError().text();
                  return false;
              } else {
                  querys.clear();
                  qDebug() << "db.commit() = "<<db.commit();
                  if(!db.commit()){
                  db.rollback();
                  }
                  return true;
              }
              return false;
          }
          Запускаю две программы с обращением к одной базе и пытаясь сделать запись получаю db.commit() = false.
          Закрываю одну программу, произвожу запись и db.commit() = true, запись прошла успешно.
          В документации написано:
          Note: For some databases, the commit will fail and return false if there is an active query using the database for a SELECT . Make the query inactive before doing the commit.
          То есть получается, что SELECT'ы одной программы блокируют базу для операций записи, редактирования и удаления другой. У меня при открытии программы интерфейс заполняется данными из моделей и mapper с использованием таких запросов:
          this->setQuery("SELECT ... ");
          И я так понимаю, что они висят активными. В документации про bool QSqlQuery::isActive() const пишут следующее:
          Returns true if the query is active . An active QSqlQuery is one that has been exec()'d successfully but not yet finished with. When you are finished with an active query, you can make the query inactive by calling finish () or clear (), or you can delete the QSqlQuery instance.

          Note: Of particular interest is an active query that is a SELECT statement. For some databases that support transactions, an active query that is a SELECT statement can cause a commit() or a rollback() to fail, so before committing or rolling back, you should make your active SELECT statement query inactive using one of the ways listed above.

          То есть все SELECT надо делать неактивными после использования.

          В документации по setQuery() обращают внимание, что запрос должен быть активным. В итоге замкнутый круг :(

            Виталий Антипов
            • Қаң. 26, 2018, 2:01 Т.Қ.

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

              Evgenii Legotckoi
              • Қаң. 26, 2018, 3:49 Т.Қ.

              Я правильно понимаю, что вы в большинстве случае используете QSqlQueryModel для интерфейса?
              Теоретически можно написать свою модель данных, которая будет использовать запросы к базе данных.

              Там просто будет вектор, с которым вы будете работать для отдачи данных и редактирования, но для обновления интерфейса будете пользоваться сырыми SQL запросами.
                Виталий Антипов
                • Қаң. 26, 2018, 4:48 Т.Қ.

                Да, в подавляющем большинстве. С такой проблемой многие столкнулись, судя по форумам. QSqlQueryModel всегда держит соединение активным и никуда от этого не уйти, жаль что этого не знал раньше. Придется львиную часть логики переписывать. На уме два варианта - формировать какие-то виртуальные таблицы и использовать setQuery() к ним, либо как вы говорите делать свою модель. Модель мне нужна только для отдачи данных, изменения в базу вношу прямыми запросами. Я так понимаю, надо QVector заполнять примерно так:

                while(query.next()) {
                    vector.append(query.value(0).toString());
                    vector.append(query.value(1).toString());
                }
                Но вот как быть с ролями? Мне пока трудно понять что представляет собой QVector.
                  Evgenii Legotckoi
                  • Қаң. 26, 2018, 5:07 Т.Қ.

                  Результат запроса можно выразить классом или структурой. Каждая структура будет иметь несколько полей, которые фактически будут являться колонками строк в запросе.

                  struct SomeRecord
                  {
                      int id;
                      QString name;
                      double value;
                  }
                  Вы будете иметь вектор с этими строками
                  QVector<SomeRecord> records;
                  Заполнять по идее надо будет так
                  while(query.next()) {
                      SomeRecord record;
                      record.id = query.value(0).toInt();
                      record.name = query.value(1).toString();
                      record.value = query.value(2).toDouble();
                      records.push_back(record);
                  }
                  А дальше с этим вектором делаете что хотите. Наиболее часто советуемый мной пример является пример из Qt Creator`a про AnimalModel. На основе него можете посмотреть как сделать рабочую модель с вектором данных внутри для отображения в таблице.
                   
                  Надеюсь, что это поможет. По хорошему, Вам бы проверить на прототипном приложении, помогают ли вообще эти транзакции для того, чтобы отпустить владение базой данных при активном подсоединении двух приложении. Я такого не пробовал, но имею предположение, что это может помочь.
                    Виталий Антипов
                    • Қаң. 26, 2018, 5:41 Т.Қ.

                    Спасибо! Только что смотрел пример AnimalModel. Вроде все понятно. А транзакции обязательно посмотрю, только в моем случае она не поможет. В документации пишут, что в некоторых случаях при транзакции SELECT db.commit() = false и необходимо закрыть запрос вручную. В моем случае использования QSqlQueryModel если сделать запросу finish(), то очистятся все данные модели, останутся только колонки и строки (первоначальная таблица стала пустой, сохранив свой размер), если clear(), то удаляется вся модель. Я тут ради интереса в обеих программах INSERT обернул в транзакции. При параллельной работе на одну базу вставка нигде не прошла. Надеюсь QVector поможет. Еще раз спасибо!

                      Виталий Антипов
                      • Қаң. 27, 2018, 6:52 Т.Ж.
                      • (өңделген)

                      Создал тестовое приложение с QSqlQueryModel. Запустил releas и debug версии с подключением к одной базе. База заблокирована. По вашему совету все таки обернул setQuery() в транзакцию:

                      void ListModel::updateModel()
                      {    
                          QSqlDatabase db = QSqlDatabase::database();
                          db.transaction();
                          this->setQuery("SELECT ... ");
                          while(this->canFetchMore()){
                             this->fetchMore();
                          }
                      if(!db.commit()){ db.rollback(); } qDebug()<<"db.commit() = "<<db.commit(); //false }
                      И о чудо! Данные добавляются, удаляются, база не блокируется. Все супер! Спасибо за совет, пошел писать транзакции на все селекты основной программы.
                        Evgenii Legotckoi
                        • Қаң. 27, 2018, 6:54 Т.Ж.

                        Вот и хорошо, что получилось всё. Успехов.

                          Виталий Антипов
                          • Қаң. 27, 2018, 8:15 Т.Ж.

                          Важное дополнение для тех кто встретится с такой же проблемой. Использование fetchMore() обязательно!

                          while(this->canFetchMore()){
                                  this->fetchMore();
                              }
                          Проверено на тесте базы с 2000 записей, просто использование транзакции не помогает. Дело в том, что модель через setQuery() не загружает все данные, а подгружает их по мере необходимости. Из-за этого запрос висит активным и блокирует базу. Выше указанный код насильно грузит все данные в модель, что в связке с транзакцией разблокирует базу.
                            Evgenii Legotckoi
                            • Қаң. 29, 2018, 6:51 Т.Ж.
                            • (өңделген)

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

                            Следует предусмотреть возможность построчного обновления таблиц при их редактировании и удалении отдельных строк с кешированием выборки SELECT на стороне приложения, чтобы не дёргать каждый раз по 100 000 записей. Иначе это будет чревато тормозами самого приложения у пользователя.
                              Виталий Антипов
                              • Қаң. 29, 2018, 10:26 Т.Ж.

                              Евгений, я провел тестирование на 337 000 записей. Результаты фантастические. Визуально скорость загрузки осталась та же что и для 2 000 записей - примерно 1 сек. Но есть нюанс - это при прямых запросах. А вот с вложенными сложнее.

                              void ListModelStatIzmerAgr::updateModel()
                              {
                                  QSqlDatabase db = QSqlDatabase::database();
                                  db.transaction();    
                                  this->setQuery("select Baza.id, Baza.KKS, (select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id) as total, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_Rezhim = 1) as hh, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_Rezhim = 2) as nom, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_Rezhim = 3) as rd, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_TipIzmerenia = 1) as pnr, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_TipIzmerenia = 2) as pnrdop, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_TipIzmerenia = 3) as ekspl, "
                                                 "(select count(*) from BazaIzmereni where BazaIzmereni.id_Baza = Baza.id and BazaIzmereni.id_TipIzmerenia = 4) as ekspldop "
                                                 "from Baza where total <> 0 order by 3 desc");
                                  while(this->canFetchMore()){
                                      this->fetchMore();
                                  }
                                  if(!db.commit()){
                                  db.rollback();
                                  }
                              }
                              Вот такой запрос для 337 000 в таблице Baza обрабатывается примерно 70 секунд. Где-то попадалось на форумах, что setQuery() не очень хорошо работает со сложными запросами и без fetchMore() все данные не вытягивает. Там смысл в том, что при простых запросах база отдает информацию об общем количестве строк и грузится все сразу, а в сложных запросах количество записей неизвестно и в Qt посчитали необходимым жестко зафиксировать получение 256 записей. Если сделать fetchMore() новое ограничение станет 512 и т.д.
                              Провел еще один эксперимент. Сначала подключился к малой базе и вывел в интерфейс кастомную таблицу с 2000 записей, а затем в программе переподключился к большой базе и вывел в интерфейс этой же моделью 337000 записей. Программа даже не задумалась - моментально все.
                                Виталий Антипов
                                • Қаң. 29, 2018, 10:47 Т.Ж.

                                У меня такое ощущение, что fetchMore() при прямых запросах ничего не делает, судя по скорости модель так же подгружается по мере необходимости. Но этот метод как-то разблокирует базу, то есть сообщает драйверу что запрос выполнен до конца и не активен. А по документации setQuery() используется только при активном запросе, и он походу и остается активным. Это все догадки конечно, исходники я не смотрел.

                                  Evgenii Legotckoi
                                  • Қаң. 29, 2018, 4:05 Т.Қ.

                                  К сожалению, ничего не могу сказать за внутренности реализации модуля SQL в Qt. Тоже не доводилось им пользоваться.

                                  Как вариант можно посмотреть ещё в сторону Wt::dbo - это орм. Требует для себя Boost, и вяжется с Qt. На работе используем для крупного проекта с использованием SQLite. Правда не уверен насчёт нескольких приложений одновременно, но точно знаю, что всё заполнение там идёт через транзакции. Мы всегда открываем транзакцию, а потом закрываем её, когда работа с базой данных была завершена. То есть был выполнен некоторый запрос и т.д.

                                  Правда это более сложных подход. Тут придётся все модели писать самостоятельно...
                                    ЭА
                                    • Шілде 19, 2023, 8:32 Т.Ж.

                                    Спасибо, этот тред оооочень помог. Долго ломал голову, почему на 257 записи всё падает.

                                      Пікірлер

                                      Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
                                      Кіріңіз немесе Тіркеліңіз
                                      Г

                                      C++ - Тест 001. Первая программа и типы данных

                                      • Нәтиже:66ұпай,
                                      • Бағалау ұпайлары-1
                                      t

                                      C++ - Тест 001. Первая программа и типы данных

                                      • Нәтиже:33ұпай,
                                      • Бағалау ұпайлары-10
                                      t

                                      Qt - Тест 001. Сигналы и слоты

                                      • Нәтиже:52ұпай,
                                      • Бағалау ұпайлары-4
                                      Соңғы пікірлер
                                      G
                                      GoattRockҚыр. 3, 2024, 1:50 Т.Қ.
                                      Linux жүйесінде файлдарды қалай көшіруге болады Задумывались когда-нибудь о том, как мы привыкли доверять свои вещи службам грузоперевозок? Сейчас такие услуги стали неотъемлемой частью нашей жизни, особенно когда речь идет о переездах между …
                                      d
                                      dblas5Шілде 5, 2024, 11:02 Т.Ж.
                                      QML - Сабақ 016. SQLite деректер қоры және онымен QML Qt-та жұмыс істеу Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                                      k
                                      kmssrАқп. 8, 2024, 6:43 Т.Қ.
                                      Qt Linux - Сабақ 001. Linux астында Autorun Qt қолданбасы как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                                      АК
                                      Анатолий КононенкоАқп. 5, 2024, 1:50 Т.Ж.
                                      Qt WinAPI - Сабақ 007. Qt ішінде ICMP Ping арқылы жұмыс істеу Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                                      Енді форумда талқылаңыз
                                      Evgenii Legotckoi
                                      Evgenii LegotckoiМаусым 24, 2024, 3:11 Т.Қ.
                                      добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
                                      F
                                      FynjyШілде 22, 2024, 4:15 Т.Ж.
                                      при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …
                                      BlinCT
                                      BlinCTМаусым 25, 2024, 1 Т.Ж.
                                      Нарисовать кривую в qml Всем привет. Имеется Лист листов с тосками, точки получаны интерполяцией Лагранжа. Вопрос, как этими точками нарисовать кривую? ChartView отпадает сразу, в qt6.7 появился новый элемент…
                                      BlinCT
                                      BlinCTМамыр 5, 2024, 5:46 Т.Ж.
                                      Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
                                      Evgenii Legotckoi
                                      Evgenii LegotckoiМамыр 2, 2024, 2:07 Т.Қ.
                                      Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.

                                      Бізді әлеуметтік желілерде бақылаңыз