SQLite可以处理9000万条记录吗?

时间:2010-07-01 19:19:36

标签: sql sqlite

或者我应该使用不同的锤子来解决这个问题。

我有一个非常简单的用例来存储数据,实际上是一个稀疏矩阵,我试图将其存储在SQLite数据库中。我创建了一个表:

create TABLE data ( id1 INTEGER KEY, timet INTEGER KEY, value REAL )

我插入了大量数据,(每10分钟800个元素,每天45次),一年中大部分时间。 (id1,timet)的元组将始终是唯一的。

时间值是自纪元以来的秒数,并且将始终在增加。出于所有实际目的,id1是随机整数。虽然可能只有20000个独特的ID。

然后我想访问id1 == someid的所有值或访问timet == sometime的所有元素。在我通过Linux上的C接口使用最新SQLite的测试中,查找其中一个(或此查找的任何变体)大约需要30秒,这对我的用例来说还不够快。

我尝试为数据库定义一个索引,但这会减慢插入到完全不可行的速度(虽然我可能做错了...)

上表导致对任何数据的访问速度非常慢。我的问题是:

  • SQLite完全是错误的工具吗?
  • 我可以定义索引以显着加快速度吗?
  • 我应该使用HDF5而不是SQL吗?

请原谅我对SQL的基本理解!

由于

我提供了一个代码示例,显示了在使用索引时插入速度如何减慢到爬行速度。使用'create index'语句,代码需要19分钟才能完成。没有它,它会在18秒内运行。


#include <iostream>
#include <sqlite3.h>

void checkdbres( int res, int expected, const std::string msg ) 
{
  if (res != expected) { std::cerr << msg << std::endl; exit(1); } 
}

int main(int argc, char **argv)
{
  const size_t nRecords = 800*45*30;

  sqlite3      *dbhandle = NULL;
  sqlite3_stmt *pStmt = NULL;
  char statement[512];

  checkdbres( sqlite3_open("/tmp/junk.db", &dbhandle ), SQLITE_OK, "Failed to open db");

  checkdbres( sqlite3_prepare_v2( dbhandle, "create table if not exists data ( issueid INTEGER KEY, time INTEGER KEY, value REAL);", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");
  checkdbres( sqlite3_prepare_v2( dbhandle, "create index issueidindex on data (issueid );", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");
  checkdbres( sqlite3_prepare_v2( dbhandle, "create index timeindex on data (time);", -1, & pStmt, NULL ), SQLITE_OK, "Failed to build create statement");
  checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
  checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");

  for ( size_t idx=0; idx < nRecords; ++idx)
  {
    if (idx%800==0)
    {
      checkdbres( sqlite3_prepare_v2( dbhandle, "BEGIN TRANSACTION", -1, & pStmt, NULL ), SQLITE_OK, "Failed to begin transaction");
      checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute begin transaction" );
      checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize begin transaction");
      std::cout << "idx " << idx << " of " << nRecords << std::endl;
    }

    const size_t time = idx/800;
    const size_t issueid = idx % 800;
    const float value = static_cast<float>(rand()) / RAND_MAX;
    sprintf( statement, "insert into data values (%d,%d,%f);", issueid, (int)time, value );
    checkdbres( sqlite3_prepare_v2( dbhandle, statement, -1, &pStmt, NULL ), SQLITE_OK, "Failed to build statement");
    checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute insert statement" );
    checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize insert");

    if (idx%800==799)
    {
      checkdbres( sqlite3_prepare_v2( dbhandle, "END TRANSACTION", -1, & pStmt, NULL ), SQLITE_OK, "Failed to end transaction");
      checkdbres( sqlite3_step( pStmt ), SQLITE_DONE, "Failed to execute end transaction" );
      checkdbres( sqlite3_finalize( pStmt ), SQLITE_OK, "Failed to finalize end transaction");
    }
  }

  checkdbres( sqlite3_close( dbhandle ), SQLITE_OK, "Failed to close db" ); 
}

8 个答案:

答案 0 :(得分:29)

您是否一次插入所有800个元素?如果是这样,在事务中执行插入操作将极大地加速该过程。

请参阅http://www.sqlite.org/faq.html#q19

SQLite可以处理非常大的数据库。见http://www.sqlite.org/limits.html

答案 1 :(得分:9)

我查看了您的代码,我认为您可能会使用preparefinalize语句过度使用它。我绝不是一个SQLite专家,但是每次循环准备一个语句都会有很大的开销。

从SQLite网站引用:

  

准备好的陈述之后   通过一次或多次调用来评估   sqlite3_step(),可以重置   要通过电话再次评估   到sqlite3_reset()。运用   现有sqlite3_reset()   准备好的声明而不是创造   新准备的声明避免   不必要的电话   sqlite3_prepare()。在许多SQL中   语句,运行所需的时间   sqlite3_prepare()等于或超过   sqlite3_step()所需的时间。   所以避免打电话   sqlite3_prepare()可以导致   显着的性能提升。

http://www.sqlite.org/cintro.html

在您的情况下,您可以尝试binding new values to your existing statement

,而不是每次都准备一份新的声明

所有这些说,我认为索引可能是真正的罪魁祸首,因为随着您添加更多数据,时间不断增加。我对此感到好奇,我计划在周末进行一些测试。

答案 2 :(得分:7)

回答我自己的问题,作为提供一些细节的地方:

事实证明(正如上面正确建议的那样)索引创建是缓慢的步骤,每次我执行另一个插入事务时,索引都会更新,这需要一些时间。我的解决方案是:  (一)创建数据表  (B)插入我所有的历史数据(价值几年)  (C)创建索引

现在所有查找等都非常快,sqlite做得很好。后续的每日更新现在需要几秒钟才能插入800条记录,但这没有问题,因为它只会每10分钟左右运行一次。

感谢Robert Harvey和maxwellb提供上述帮助/建议/答案。

答案 3 :(得分:5)

由于我们知道当表上没有索引时捕获数据很快,实际上可能有用的是:

  1. 在没有索引的临时表中捕获800个值。

  2. 使用带有SELECT语句的INSERT INTO形式将记录复制到主表(包含索引)。

  3. 从临时表中删除记录。

  4. 这种技术基于以下理论:采用SELECT语句的INSERT INTO比执行单个INSERT更快。

    如果仍然证明有点慢,可以使用Asynchronous Module在后台执行第2步。这利用了捕获之间的停机时间。

答案 4 :(得分:3)

考虑将表用于给定日期的新插入,而不使用索引。然后,在每天结束时,运行一个脚本:

  1. 将new_table中的新值插入master_table
  2. 清除第二天处理的new_table
  3. 如果您可以在O(log n)中查找历史数据,并在O(n)中查找今天的数据,这应该是一个很好的折衷方案。

答案 5 :(得分:2)

我无法从您的规格中看出来,但是如果ID字段总是在增加,并且时间字段包含YYYYMMDD以获得唯一性并且也一直在增加,并且您正在进行ID搜索或时间搜索,那么最简单的非数据库解决方案是简单地将所有记录附加到固定字段文本或二进制文件(因为它们是以“排序”顺序生成)并使用代码对所需记录进行二进制搜索(例如,找到首先记录感兴趣的ID或时间,然后按顺序逐步调整所需的范围。

答案 6 :(得分:0)

构建大型SQLite数据库时,请始终在创建索引之前插入尽可能多的数据。这比在插入数据之前创建索引要快许多倍。

答案 7 :(得分:0)

The theoretical maximum number of rows in a table is 2^64 (18446744073709551616 or about 1.8e+19). This limit is unreachable since the maximum database size of 140 terabytes will be reached first. A 140 terabytes database can hold no more than approximately 1e+13 rows, and then only if there are no indices and if each row contains very little data.