使用C ++的SQL - 瓶颈在哪里?

时间:2017-02-27 02:52:07

标签: c++ mysql linux mariadb

我尝试逐行读取纯文本文件,构造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查询。为此,我尝试了一些事情。一个选项是stringstreams。

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&lt;&lt;中测量的。在std :: basic_ostream中。

我最初尝试使用字符串,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比字符串略快(但是在合理的误差范围内)。 sprintf()比两者快10%左右,但这还不够快。

当然,有一种完善的方法可以用如此大的数据集完成这项任务。我现在感激任何指导。

修改

哇,哇。我随心所欲地注释掉对mysql_query()的调用。事实证明,尽管valgrind说的是,我所有的减速都在哪里。如果没有这个块,它会从每秒200行跳到每秒120万行。那更像是它!太糟糕了,我需要数据库中的数据...

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

更加好奇。在此先感谢您的帮助!

1 个答案:

答案 0 :(得分:0)

INSERTing每行INSERT语句100行将以10倍的速度运行。

您需要每天插入120M行吗?那是每秒1400。你有没有计算过剩磁盘空间的时间?

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

您将如何处理数十亿行数据?请记住,即使使用SSD,对于索引不佳的表格形成不良的SELECT也需要很长时间。

可以对一个字符串进行规范化吗?

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

让我们看看SHOW CREATE TABLE和主SELECTs

innodb_flush_log_at_trx_commit的价值是多少?使用2。