【问题标题】:Using prepared statements in Qt在 Qt 中使用准备好的语句
【发布时间】:2018-08-14 02:24:19
【问题描述】:

我想将(自定义)类“表”的数据插入到我的数据库中 - 数据是从内部文件中检索的,但为了安全起见,我想假设来源不受信任。目前,我正在使用以下代码的变体。这显然容易受到 SQL 注入的影响,因为 v.toString() 可能会被利用。 table.getName() 会根据表白名单进行检查,因此这里不需要防御 SQL 注入。

QString rowQuery = "INSERT INTO " + table.getName() + " VALUES ";
for (Row* r : table) {
    rowQuery += "(";
    for (QVariant v : r.getValues()) {
        rowQuery += v.toString();
        rowQuery += ","
    }
    rowQuery.chop(1);
    rowQuery += "),";
}
rowQuery.chop(1);
QSqlQuery::exec(rowQuery)

包含两个条目和三列的示例查询如下所示:

INSERT INTO DebugTable VALUES (cell1x1, cell1x2, cell1x3), (cell2x1, cell2x2, cell2x3)

为了使我的应用程序免受 SQL 注入攻击,我开始使用预准备语句。不幸的是,这种设计选择极大地影响了性能,因此我尝试使用 QVariantLists 使用批处理执行,但即使这种方法也无法提供适当的性能。我最大的表有 15,000 个条目;如果没有准备好的语句,插入 (q.exec() / q.execBatch()) 大约需要 4 秒,而使用准备好的语句需要 90 秒。

QList<QVariantList> columnVectors;
QString queryString;
queryString = "INSERT INTO " + table.getName() + " VALUES (";
for (auto i : table.getCols()) {
    columnVectors.append(QVariantList());
    queryString += "?,";
}
queryString.chop(1);
queryString += ")";
for (Row* row : table.getRows()) {
    for (int i = 0; i < row->getValues().length(); i++) {
        columnVectors[i].append(variant);
    }
}
QSqlQuery q;
q.prepare(queryString);
for (QVariantList columnVector : columnVectors) {
    q.addBindValue(columnVector);
}
q.execBatch();

一个包含 x 个三列条目的示例查询如下所示:

INSERT INTO DebugTable VALUES (?, ?, ?)

我认为我的方法/实现中可能存在错误,因为我读到准备好的语句应该可以提高性能。任何帮助将不胜感激,谢谢

【问题讨论】:

  • 有趣。但你能让它更简单吗?就像为这两个示例提供一个固定的查询字符串一样。 (并衡量其表现)

标签: sql qt prepared-statement sql-injection qsqlquery


【解决方案1】:

这是一个很难回答的问题,因为数据库性能可能取决于很多其他因素。所以让我先直接回答你的问题,然后建议我认为你可能希望做的事情(如果你还没有尝试过的话),最后解释我的实验和我得到的结果,以便其他人可以重复。

首先,让我描述一种设置同时插入多行的预准备语句的方法(请记住,我将解释我的实验,因此您现在可以忽略计时器)。我使用了两个 for 循环,看起来像这样:

void stringBuilderPreparedFunction()
{
  QElapsedTimer timer;
  timer.start();
  QList<Translation> list = getList();
  QString queryString("INSERT INTO test (val, english, spanish) VALUES ");
  for (int x = 0; x < list.size(); x++)
  {
    QString rowId = QString::number(x);
    queryString.append("(:val" + rowId + ", :english" + rowId + ", :spanish" + rowId + "),");
  }
  queryString.chop(1);
  QSqlQuery query;
  query.prepare(queryString);
  for (int y = 0; y < list.size(); y++)
  {
    QString rowId = QString::number(y);
    QString numberString(":val" + rowId);
    query.bindValue(numberString, QString::number(list.at(y).number));
    QString englishString(":english" + rowId);
    query.bindValue(englishString, list.at(y).english);
    QString spanishString(":spanish" + rowId);
    query.bindValue(spanishString, list.at(y).spanish);
  }
  query.exec();
  qDebug() << "The string builder perpared took" << timer.elapsed() << "milliseconds";
}

现在下一部分要提到的是,很多时候将所有插入包装在事务中将获得与编写单个插入语句大致相同的性能。它们是非常相似的动作。因此,在您确定上面的复杂字符串附加方法之前,您也可以考虑以下代码:

void transactionFunction()
{
  QElapsedTimer timer;
  timer.start();
  QList<Translation> list = getList();
  QSqlDatabase::database().transaction();
  for (Translation row: list)
  {
    QSqlQuery query;
    query.prepare("INSERT INTO test (val, english, spanish) VALUES (:val, :english, :spanish)");
    query.bindValue(":val", row.number);
    query.bindValue(":english", row.english);
    query.bindValue(":spanish", row.spanish);
    query.exec();
  }
  QSqlDatabase::database().commit();
  qDebug() << "The transaction function took" << timer.elapsed() << "milliseconds";
}

我不确定您要执行的操作的详细信息,以及您对性能影响的敏感程度。我的结果与您的要求相比略有优势,但交易可能就足够了,而且代码更少。还要对性能结果持保留态度,因为这不是测试数据库性能的最科学方法。

对于实验,我在 MySQL 数据库中使用了一个表:

CREATE TABLE test (
    val INTEGER PRIMARY KEY,
    english VARCHAR(255),
    spanish VARCHAR(255)
);

然后我插入了 500 行,如下所示:

returnList.append(Translation(500, "five hundred", "quinientos"));

这产生了第一个函数的结果: “字符串生成器耗时 116 毫秒” 和 “交易功能耗时 138 毫秒”。与单行插入相比,这两项都是显着改进,耗时约 20 秒。

我希望对您有所帮助,如果我在您的问题中错过了标记,或者您的环境产生了不同的结果,请随时发表评论。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-29
    • 1970-01-01
    • 1970-01-01
    • 2014-06-22
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多