问题引入

在文章的最开始,先提出我在使用 QSqlQuery 时提出的两个问题,后面会得到答案:

  • 1、QSqlQueryModel 内部是怎样实现的,数据查询时是以什么方式? 批量 或者 单条 ?结果怎么放入model的,是 单条 ,还是其他高科技
  • 2、使用 pgadmin3 查询数据时,如果数量大会提示内存异常问题,不显示任何查询结果,但是使用Qt进行查询,可以显示部分结果。

这里简单说明下测试机器配置:

  • win7 64 旗舰版
  • 内存8G,频率 2133
  • vs2017专业版
  • 数据量: 600万左右的测试数据。

    分析基础

    QT为我们提供了通用的数据库操作类, QSqlqueryQSqlQueryModel 。在操作数据库时,在连接操作完成的情况下,一般都有如下操作代码: ```cpp // QSqlQuery QSqlQuery query; bool bussucess = query.exec(“sql语句”);

//QSqlQueryModel QSqlQueryModel *model = new QSqlQueryModel; model->setQuery(“sql语句”);

  1. 二者都能执行SQL语句,必然有某种相关性,所以通过阅读源码,来查看关系。通过阅读 `QSqlQueryModel` 的执行函数,我们就可以看出来了。
  2. ```cpp
  3. void QSqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
  4. {
  5. setQuery(QSqlQuery(query, db));
  6. }

其实就是 QSqlQueryModel 委托 QSqlQuery 执行查询。大概流程如下:
image.png

源码分析

上面我简单绘制了QSqlQueryModelQSqlQuery 流程,给出了关键调用的流程函数,但是依然没有到数据库执行层面,我们深入 QSqlResult 进行源码分析

  1. //E:\Qt\5.15.0\Src\qtbase\src\sql\kernel\qsqlresult.cpp
  2. /*!
  3. Sets the current query for the result to \a query. You must call
  4. reset() to execute the query on the database.
  5. \sa reset(), lastQuery()
  6. */
  7. void QSqlResult::setQuery(const QString& query)
  8. {
  9. Q_D(QSqlResult);
  10. d->sql = query;
  11. }

其实我们重要的看上面注释的意思: 你必须调用reset()去数据库执行查询 。我们在改文件中搜索 reset() 函数,我们发现该函数已被屏蔽,并且也说出了原因。

  1. /*!
  2. \fn bool QSqlResult::reset(const QString &query)
  3. Sets the result to use the SQL statement \a query for subsequent
  4. data retrieval.
  5. Derived classes must reimplement this function and apply the \a
  6. query to the database. This function is only called after the
  7. result is set to an inactive state and is positioned before the
  8. first record of the new result. Derived classes should return
  9. true if the query was successful and ready to be used, or false
  10. otherwise.
  11. \sa setQuery()
  12. */

其中很重要的一句话: Derived classes must reimplement this function and apply the \a query to the database. 派生类必须重新实现此函数并应用 查询数据库. 所以我们搜寻继承自 QSqlResult 的类,结果有两个:

  • QSQLiteResult: 继承自 QSqlCachedResult ,QSqlCachedResult 继承QSqlResult。
  • QPSQLResult: 直接继承自QSqlResult。

我是用的数据库不是 SQLite ,故我们不去看它,我们看另外一个QPSQLResult。

  1. class QPSQLResult final : public QSqlResult
  2. {
  3. Q_DECLARE_PRIVATE(QPSQLResult)
  4. public:
  5. QPSQLResult(const QPSQLDriver *db);
  6. ~QPSQLResult();
  7. QVariant handle() const override;
  8. void virtual_hook(int id, void *data) override;
  9. protected:
  10. void cleanup();
  11. bool fetch(int i) override;
  12. bool fetchFirst() override;
  13. bool fetchLast() override;
  14. bool fetchNext() override;
  15. bool nextResult() override;
  16. QVariant data(int i) override;
  17. bool isNull(int field) override;
  18. bool reset(const QString &query) override;
  19. int size() override;
  20. int numRowsAffected() override;
  21. QSqlRecord record() const override;
  22. QVariant lastInsertId() const override;
  23. bool prepare(const QString &query) override;
  24. bool exec() override;
  25. };
  1. //E:\Qt\5.15.0\Src\qtbase\src\plugins\sqldrivers\psql\qsql_psql.cpp
  2. bool QPSQLResult::reset(const QString &query)
  3. {
  4. Q_D(QPSQLResult);
  5. cleanup();
  6. if (!driver())
  7. return false;
  8. if (!driver()->isOpen() || driver()->isOpenError())
  9. return false;
  10. d->stmtId = d->drv_d_func()->sendQuery(query);
  11. if (d->stmtId == InvalidStatementId) {
  12. setLastError(qMakeError(QCoreApplication::translate("QPSQLResult",
  13. "Unable to send query"), QSqlError::StatementError, d->drv_d_func()));
  14. return false;
  15. }
  16. if (isForwardOnly())
  17. setForwardOnly(d->drv_d_func()->setSingleRowMode());
  18. d->result = d->drv_d_func()->getResult(d->stmtId);
  19. if (!isForwardOnly()) {
  20. // Fetch all result sets right away
  21. while (PGresult *nextResultSet = d->drv_d_func()->getResult(d->stmtId))
  22. d->nextResultSets.push(nextResultSet);
  23. }
  24. return d->processResults();
  25. }

我们从代码中可以看出 QPSQLResult 获取数据时一条一条获取, push() 到容器当中。到此,我们知道了 QSqlquery获取数据过程.但是我们任然不知道具体原因。
libpq-C库
31.5 逐行检索查询结果 中有一个很亮眼的 小心 .这里我贴出来:
image.png