在Qt中使用预处理语句

时间:2018-03-06 09:19:51

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

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

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

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

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();

为了使我的应用程序对SQL注入安全,我开始使用预准备语句。不幸的是,这种设计选择对性能造成很大影响,因此我尝试使用QVariantLists进行批量执行,但即使这种方法也无法提供适当的性能。我最大的桌子有15,000个参赛作品;如果没有预备语句,插入(q.exec()/ q.execBatch())大约需要4秒钟,准备好的语句需要花费90秒。

INSERT INTO DebugTable VALUES (?, ?, ?)

使用带有三列的 x 条目的示例查询如下所示:

types {
    text/html                               html htm shtml;
    text/css                                css;
    text/xml                                xml rss;
    image/gif                               gif;
    image/jpeg                              jpeg jpg;
    application/x-javascript                js;
    application/atom+xml                    atom;}

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

1 个答案:

答案 0 :(得分:0)

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

首先,让我描述一种建立准备好的语句的方法,该语句可以同时插入多行(请记住,我将解释我的实验,因此您现在可以忽略计时器)。我使用了两个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秒。

我希望能有所帮助,并且如果我错过了您所问的内容,或者您​​的环境是否产生了不同的结果,请随时发表评论。