【问题标题】:SQL using C++ - Where is the bottleneck?使用 C++ 的 SQL - 瓶颈在哪里?
【发布时间】:2017-07-17 13:18:10
【问题描述】:

我正在尝试逐行读取纯文本文件,构建 SQL INSERT 语句,执行查询,然后继续。目前,我有一个可以在我大约 4 岁的桌面上每秒完成大约 200 行的解决方案。但是,我有大约 1.2 亿行要处理,并希望将其作为日常任务来实现。花几个小时完成它会很好,但花将近一周的时间是不可行的。

这些行将包含一个字符串和 5 到 9 个整数,范围从布尔值(我编码为 TINYINT(1))到午夜后的微秒 (BIGINT)。

一旦从文件中读入(通过 getline()),这些行就会被这个函数标记化:

#define MAX_TOKENS 10
#define MAX_TOKEN_LENGTH 32

char tokens[MAX_TOKENS][MAX_TOKEN_LENGTH];

//...

void split_line(const string &s)
{
  char raw_string[MAX_TOKENS * MAX_TOKEN_LENGTH];
  char *rest;
  char *token_string;

  strcpy(raw_string, s.c_str());

  if(tokens[0][0] != '\0')
  {
    fill(tokens[0], tokens[0]+(MAX_TOKENS*MAX_TOKEN_LENGTH), '\0');
  }

  for(uint32_t token = 0; token < MAX_TOKENS; token++)
  {
    if(token == 0) token_string = strtok_r(raw_string, " ", &rest);
    else token_string = strtok_r(nullptr, " ", &rest);

    if(token_string == nullptr) break;

    if(token >= 1)
    {
      //if it's not a number...
      if(token_string[0] < 48 || token_string[0] > 57)
      {
        if(token_string[0] != 45) //negative numbers are allowed
        {
          clear_tokens();
          break;
        }
      }
    }

    strcpy(tokens[token], token_string);
  }
}

我曾尝试过该标记器的更多 STL 派生版本,但事实证明这太慢了。它在调用图中的排名仍然很高,但没有正确的 STL 字符串那么高。

无论如何,下一步是构建 SQL 查询。为此,我尝试了一些事情。一种选择是字符串流。

string insert_query = "INSERT INTO data_20170222";
stringstream values;
string query;

while(getline(input_stream, input_stream_line))
{
  split_line(input_stream_line);

  if(tokens[5][0] != '\0')  //the smallest line will have six tokens
  {
    try
    {
      query = insert_query;
      uint32_t item_type = stoi(tokens[2]);

      switch(item_type)
      {
        case 0: //one type of item
        case 1: //another type of item
        {
          values << " (valueA, valueB, valueC, valueD, valueE, valueF,"
                    " valueG, valueH) values('"
                 << tokens[0] << "', " << tokens[1] << ", "
                 << tokens[2] << ", "  << tokens[3] << ", "
                 << tokens[4] << ", "  << tokens[5] << ", "
                 << tokens[6] << ", "  << tokens[7] << ")";
          break;
        }
        //...
      }

        query.append(values.str());
        values.str(string());
        values.clear();

        if(mysql_query(conn, query.c_str()))
        {
          string error(mysql_error(conn));
          mysql_close(conn);
          throw runtime_error(error);
        }
      }
      catch(exception &ex)
      {
        cerr << "Error parsing line\n  '" << input_stream_line
             << "'\n" << "  " << ex.what() << endl;
        throw;
      }
    }

当我运行这个版本时,我看到 30% 的 callgrind 样本是在 std::operator

我最初尝试用字符串来做这一切,ala:

string values;
values = " (valueA, valueB, valueC, valueD, valueE, valueF,"
         " valueG, valueH) values('" +
         string(tokens[0]) + "', " + tokens[1] + ", "
         tokens[2] + ", "  + tokens[3] + ", "
         tokens[4] + ", "  + tokens[5] + ", "
         tokens[6] + ", "  + tokens[7] + ")";

事实证明这实际上是相同的速度,但这次将 30% 的样本从 std::basic_string 分配给 std::operator+。

最后,我切换到了直接的 sprintf()。

char values[MAX_TOKENS * MAX_TOKEN_LENGTH];
sprintf(values, " (valueA, valueB, valueC, valueD, valueE, valueF,"
        " valueG, valueH) values('%s', %s, %s, %s, %s, %s, %s, %s)",
        tokens[0], tokens[1], tokens[2], tokens[3],
        tokens[4], tokens[5], tokens[6], tokens[7]);

stringstream 比 string 稍快(不过,在合理的误差范围内)。 sprintf() 比两者都快了大约 10%,但这仍然不够快。

肯定有一种行之有效的方法可以使用如此庞大的数据集完成这项任务。在这一点上,我将不胜感激。

编辑

哇哦。我一时兴起注释掉了对 mysql_query() 的调用。事实证明,不管 valgrind 怎么说,这就是我所有减速的地方。如果没有那个块,它会从每秒 200 行跃升至每秒 120 万行。这还差不多!太糟糕了,我需要数据库中的数据...

我想这已经成为一个问题,为什么 MariaDB 现在似乎运行如此缓慢。我在这个系统中有一个很好的 SSD、16GB RAM 等。我觉得我的硬件不太可能阻止它。

更加好奇。提前感谢您的帮助!

【问题讨论】:

  • 您可能需要在事务中执行此操作。如果您的数据库设置针对您的机器进行了优化,这也可能会有所帮助。
  • 事务内部是什么意思?
  • 一个数据库事务。阅读您的数据库引擎的文档。此外,如果您对每一行重复相同的INSERT 查询,只是使用不同的值,请考虑在读取文件之前预先准备一次查询,然后每次重新运行准备好的查询,为其赋予新的值。准备好的参数化查询比一遍又一遍地运行全新的查询要快得多。
  • 大多数/所有数据库都有外部实用程序(或其他功能)来读取文件,而无需编写任何比 cfg 文件和 cmd 行更多的代码,即bcp -u$uid -p$pswd -D$db -t$tbl -c$cfgFile -i$InFile -$l${logFile} -e${errFile"(或类似的)。询问您的组织,一定有人已经弄清楚了这一点(它将是高度优化的代码,具有强大的错误检测功能(您必须学习如何破译;-))。祝你好运。
  • 由于你没有提供你正在执行的 SQL,你的问题现在跑题了。

标签: c++ mysql linux mariadb


【解决方案1】:

批处理INSERTing 100 行每个INSERT 语句的运行速度将提高10 倍。

您是否需要每天插入 1.2 亿行?那是每秒 1400 次。您是否计算过多久会耗尽磁盘空间?

让我们看看SHOW CREATE TABLE。当INT(4 字节)就足够时,不要使用BIGINT(8 字节)。当MEDIUMINT(3 个字节)可以使用时,不要使用INT。等等。

您将如何处理海量数据?请记住,格式不正确的 SELECT 与索引不佳的表将需要很长时间,即使使用 SSD。

一个字符串可以标准化吗?

考虑将一堆布尔值打包成 SET(每 8 个布尔值 1 个字节)或某种大小的 int。

让我们看看SHOW CREATE TABLE 和主要的SELECTs

innodb_flush_log_at_trx_commit 的值是多少?使用 2。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-08
    • 2012-12-03
    • 1970-01-01
    • 2011-05-25
    • 2021-07-08
    • 1970-01-01
    • 1970-01-01
    • 2011-07-04
    相关资源
    最近更新 更多