Russian Qt Forum
Апрель 18, 2024, 08:20 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: QSqlTableModel + SQLite ооочень медленно  (Прочитано 8021 раз)
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« : Октябрь 19, 2016, 20:42 »

Всем привет.

Собственно, я слышал, что стандартные SQL модели медленные, но чтобы настолько...  Шокированный

В общем, имею табличку на 10 записей... Каждые 500 мсек в нее добавляется по одной записи
в ячейку от 0 до 9 по очереди. В конце заполнения строки модели делается submitAll().

Так воот, этот  QSqlTableModel::submitAll() ну уж оочень медленный..

В моем случае оно занимает 60~100 мсек  Шокированный Шокированный Шокированный

Это уже становится заметным, например, если взять окошко и начать его таскать
по рабочему столу, то в момент коммита данных, его перемещение рывком останавливается
(т.е. видно дерганье окошка при перемещении).

Так вот, вопрос: это или у меня лыжи кривые или я что-то недопонял.

Ниже привожу код и проект, мож кто подскажет что делать... (не писать же все самому с нуля??)

= main.cpp =

Код
C++ (Qt)
#include "widget.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   Widget w;
   w.show();
 
   return a.exec();
}
 

= widget.h =

Код
C++ (Qt)
#ifndef WIDGET_H
#define WIDGET_H
 
#include <QTableView>
 
class QSqlTableModel;
class QTimer;
 
class Widget : public QTableView
{
   Q_OBJECT
public:
   Widget(QWidget *parent = nullptr);
   ~Widget();
private:
   enum { MaxColumn = 10 };
   void startRow();
   void commitRow();
   void setupValue();
 
   QString m_connection;
   QSqlTableModel *m_model = nullptr;
   QTimer *m_timer = nullptr;
   int m_column = MaxColumn;
};
 
#endif // WIDGET_H
 

= widget.cpp =

Код
C++ (Qt)
#include "widget.h"
 
#include <QTimer>
#include <QElapsedTimer>
#include <QUuid>
 
#include <QSqlTableModel>
#include <QSqlDatabase>
#include <QSqlQuery>
 
#include <QDebug>
 
Widget::Widget(QWidget *parent)
   : QTableView(parent)
   , m_timer(new QTimer(this))
   , m_connection(QUuid::createUuid().toString())
{
   QSqlDatabase db = QSqlDatabase::addDatabase(QLatin1String("QSQLITE"), m_connection);
   db.setDatabaseName(QLatin1String("foo.db"));
 
   Q_ASSERT(db.open());
 
   const QString table = QLatin1String("foo");
   QString create = QLatin1String("create table if not exists ") + table + QLatin1Char('(');
   for (int i = 0; i < MaxColumn; ++i) {
       const QString n = QChar::fromLatin1(65 + i);
       if (i + 1 == MaxColumn)
           create += n + QLatin1String(" blob");
       else
           create += n + QLatin1String(" blob,");
   }
   create += QLatin1Char(')');
 
   QSqlQuery query(db);
   query.prepare(create);
   Q_ASSERT(query.exec());
 
   m_model = new QSqlTableModel(this, db);
   m_model->setEditStrategy(QSqlTableModel::OnManualSubmit);
   m_model->setTable(table);
   m_model->select();
 
   setModel(m_model);
 
   connect(m_timer, &QTimer::timeout, [this]() {
       if (m_column == MaxColumn) {
           commitRow();
           startRow();
       } else {
           setupValue();
       }
 
       if (m_column < MaxColumn)
           ++m_column;
       else
           m_column = 0;
   });
 
   m_timer->start(500);
}
 
Widget::~Widget()
{
   delete m_model;
   {
       QSqlDatabase db = QSqlDatabase::database(m_connection);
       db.close();
   }
   QSqlDatabase::removeDatabase(m_connection);
}
 
void Widget::startRow()
{
   const int rows = m_model->rowCount();
   Q_ASSERT(m_model->insertRow(rows));
}
 
void Widget::commitRow()
{
   QElapsedTimer et;
   et.start();
   m_model->submitAll();
   qDebug() << "spent:" << et.elapsed();
}
 
void Widget::setupValue()
{
   const auto row = m_model->rowCount() - 1;
   Q_ASSERT(row >= 0);
   const auto index = m_model->index(row, m_column);
   m_model->setData(index, m_column);
}
 

Вывод приложения такой:

Цитировать
Debugging starts
spent: 1
spent: 59
spent: 59
spent: 65
spent: 65
spent: 65
spent: 64
spent: 67
spent: 66
spent: 82
spent: 56
spent: 98
Debugging has finished

Тестил на Windows 10 & Qt 5.7.0 & MSVC2015 & 32 bit
Записан

ArchLinux x86_64 / Win10 64 bit
Bepec
Гость
« Ответ #1 : Октябрь 19, 2016, 21:10 »

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

По сути sqlite в момент коммита производит запись на диск данных. Т.к. у неё нет собственного процесса, всё это происходит в момент коммита.
Следовательно 60+ мс это время занесения записи в бд и записи на диск.

И скорее всего у вас HDD. Ибо вот мой лог:
Цитировать
Отладка запущена
spent: 0
spent: 7
spent: 7
spent: 4
spent: 5
spent: 6
spent: 6
spent: 8
spent: 6
Отладка завершена

PS интересно кстати, как вариант, попробовать перенести модель в отдельный поток. Хотя тема о переносе поднималась, вроде никто результатов не озвучивал.
« Последнее редактирование: Октябрь 20, 2016, 04:33 от Bepec » Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #2 : Октябрь 20, 2016, 06:16 »

можно попробовать submit обернуть в транзакцию
Записан

Юра.
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #3 : Октябрь 20, 2016, 08:54 »

Цитировать
И скорее всего у вас HDD.

Ну да, рабочий комп у меня еще Тутанхамона видел. И это плюс, т.к. на нем сразу видны все узкие места приложения. Улыбающийся

Цитировать
Следовательно 60+ мс это время занесения записи в бд и записи на диск.

Да ну, не верю, не может запись пары десятков байт занимать 60 мсек.. Да и ваши 6-8 мсек  - это, ИМХО, оочень много.

Цитировать
интересно кстати, как вариант, попробовать перенести модель в отдельный поток.

Брр... это не то на что я расчитывал изначально, т.к. это добавляет геммороя.

Цитировать
можно попробовать submit обернуть в транзакцию

А каким образом?  Строит глазки
Записан

ArchLinux x86_64 / Win10 64 bit
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #4 : Октябрь 20, 2016, 09:16 »

UPD: Только что проверил на домашнем компе (с HDD), но под линукс.

Qt 5.7.0 & GCC 6.2.1 & 64 bit:

Цитировать
spent: 0
spent: 35
spent: 60
spent: 51
spent: 65
spent: 65
spent: 71
spent: 63
spent: 29
spent: 44
spent: 35
spent: 60
spent: 33
spent: 58

Qt 4.8.7 & GCC 6.2.1 & 64 bit:

Цитировать
spent: 0
spent: 31
spent: 30
spent: 63
spent: 28
spent: 52
spent: 35
spent: 50
spent: 32
spent: 57
spent: 30
spent: 47
spent: 29
spent: 53

Рывков вроде не наблюдаю.
Записан

ArchLinux x86_64 / Win10 64 bit
Bepec
Гость
« Ответ #5 : Октябрь 20, 2016, 12:13 »

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

Достигнуто кодом
Код:
   {
         QSqlQuery query(db);
          query.prepare("PRAGMA synchronous = OFF");
          query.exec();
    }
результат
Цитировать
Отладка запущена
spent: 0
spent: 6
spent: 2
spent: 1
spent: 2
spent: 2
spent: 1
spent: 1
spent: 2
spent: 1
spent: 2
Отладка завершена


Можно ещё поиздеваться и изменить флаг temp_store

Код:
   {
         QSqlQuery query(db);
          query.prepare("PRAGMA temp_store = MEMORY");
          query.exec();
    }

Цитировать
Отладка запущена
spent: 0
spent: 1
spent: 2
spent: 1
spent: 1
spent: 1
spent: 1
spent: 1
spent: 1
spent: 1
spent: 2
spent: 1
spent: 2
Отладка завершена


PS по умолчанию sqlite работает в драконовском режиме безопасности, но это гарантирует отсутствие потери данных.

PPS вероятно, он ожидает окончания записи файла на диск, и производит проверку результата.
« Последнее редактирование: Октябрь 20, 2016, 12:17 от Bepec » Записан
lit-uriy
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3880


Просмотр профиля WWW
« Ответ #6 : Октябрь 20, 2016, 14:09 »

>А каким образом?
QSqlDatabase QSqlTableModel::database()
bool QSqlDatabase::transaction()
QSqlDatabase::commit()

Однако, если модель действительно сама создаёт транзакции, то будут проблемы
Записан

Юра.
Bepec
Гость
« Ответ #7 : Октябрь 20, 2016, 14:49 »

Транзакция у меня на машине увеличила время обработки до 8 мс ...
Записан
Vamireh
Гость
« Ответ #8 : Октябрь 23, 2016, 21:06 »

Когда работал с SqlLite до начала запросов выполнял "BEGIN;", а после "COMMIT;" Во всяком случае в этом конкретном примере spent с 79-102 упал до 1-2. Правда, в случае краша приложения потеряем все.
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #9 : Октябрь 25, 2016, 10:30 »

О, погуглив, набрел на такую "фишку" SQLite как режим WAL

Включать вроде как-то так (вроде как поддерживается для SQLite > 3.7.0):

Цитировать
sql.exec("PRAGMA journal_mode = WAL")

но я сам еще не пробовал. Улыбающийся

PS: На этом форуме уже была похожая тема, но я так и не понял,
заработало оно или нет.
« Последнее редактирование: Октябрь 25, 2016, 10:37 от kuzulis » Записан

ArchLinux x86_64 / Win10 64 bit
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.235 секунд. Запросов: 23.