或者我应该使用不同的锤子来解决这个问题.
我有一个非常简单的用例来存储数据,实际上是一个稀疏矩阵,我试图将其存储在SQLite数据库中.我创建了一个表:
create TABLE data ( id1 INTEGER KEY, timet INTEGER KEY, value REAL )
Run Code Online (Sandbox Code Playgroud)
我插入了大量数据(每10分钟800个元素,每天45次),一年中大部分时间.(id1,timet)的元组将始终是唯一的.
时间值是自纪元以来的秒数,并且将始终增加.出于所有实际目的,id1是随机整数.虽然可能只有20000个独特的ID.
然后我想访问id1 == someid的所有值或访问timet == sometime的所有元素.在我通过Linux上的C接口使用最新SQLite的测试中,查找其中一个(或此查找的任何变体)大约需要30秒,这对我的用例来说还不够快.
我尝试为数据库定义一个索引,但这减慢了插入到完全不可行的速度(虽然我可能做错了...)
上表导致对任何数据的访问速度非常慢.我的问题是:
请原谅我对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" );
}
Run Code Online (Sandbox Code Playgroud)
Rob*_*vey 29
您是否一次插入所有800个元素?如果是这样,在事务中执行插入操作将极大地加速该过程.
见http://www.sqlite.org/faq.html#q19
SQLite可以处理非常大的数据库.见http://www.sqlite.org/limits.html
我查看了你的代码,我认为你可能会因为prepare
和finalize
语句而过度使用它.我绝不是SQLite专家,但每次循环准备一个语句都需要花费很大的开销.
引用SQLite网站:
在通过一次或多次调用评估准备好的语句之后
sqlite3_step()
,可以重置它以便通过调用再次进行评估sqlite3_reset()
.使用sqlite3_reset()
在现有的准备语句,而创建一个新的准备好的声明中避免不必要的来电sqlite3_prepare()
.在许多SQL语句中,运行所需的时间sqlite3_prepare()
等于或超过所需的时间sqlite3_step()
.因此,避免调用sqlite3_prepare()
可以显着提高性能.
http://www.sqlite.org/cintro.html
在您的情况下,您可以尝试将新值绑定到现有语句,而不是每次都准备一个新语句.
所有这些说,我认为索引可能是真正的罪魁祸首,因为随着您添加更多数据,时间不断增加.我对此感到好奇,我计划在周末进行一些测试.
回答我自己的问题,作为一个放置一些细节的地方:
事实证明(如上正确建议的),该索引创建是慢步骤,每次我执行插入的另一个交易时间,更新索引这需要一些时间.我的解决办法是:(A)创建数据表(B)插入我的所有历史数据(几年价值)(C)创建索引
现在所有查找等都非常快,sqlite做得很好.随后每日更新,现在需要几秒钟只插入800条记录,但是这是没有问题的,因为它只能运行每隔10分钟左右.
感谢Robert Harvey和maxwellb提供上述帮助/建议/答案.
考虑在没有索引的情况下为给定日期的新插入使用一个表。然后,在每天结束时,运行一个脚本,该脚本将:
如果您可以在 O(log n) 中查找历史数据,并在 O(n) 中查找今天的数据,这应该是一个很好的折衷方案。