【发布时间】:2015-11-04 01:15:51
【问题描述】:
我正在使用 SQLite 的 C 接口,并且对 rowid 字段以及如何有效地从具有已知 rowid 的任意行集中检索数据有一些基本问题。我实际上有几个相关的问题,所以我会在我去的时候用粗体说出来。但我的主要问题在最后。
我有一张桌子:
sqlite3_exec( db, "create table mytable ( value BLOB, value2 TEXT ) )", NULL, NULL, NULL );
我用 230 万行填充它。我还在表上创建了两个索引:
sqlite3_exec( db, "CREATE INDEX r_index ON mytable (rowid)", NULL, NULL, &errorMessage );
sqlite3_exec( db, "CREATE INDEX v_index ON mytable (value)", NULL, NULL, &errorMessage );
我知道 rowid 索引是不必要的。我看到 SQLite 需要 0 秒来“创建”rowid 索引,我相信这是因为 rowid 始终是表上隐含的现有“索引”,因为该表(通常?)以 rowid 顺序存储。
无论如何,我希望能够通过 rowid 从该表中快速检索任意行集。我所做的是创建一个内存中的记录列表:
class MyInMemoryIndexElement
{
public:
sqlite3_int64 _rowId;
MyKeyType _key;
}
vector<ObjectsInMemoryIndexElement> inMemoryIndex;
rc = sqlite3_prepare_v2( db, "select rowid, value from mytable" ), -1, &stmt, NULL );
for ( ; sqlite3_step( stmt ) == SQLITE_ROW ; )
{
MyInMemoryIndexElement e;
e._rowId = sqlite3_column_int64( stmt, 0 );
e._key = GetMyKeyFromValueBlob( sqlite3_column_blob( stmt, 1 ) );
inMemoryIndex.push_back( e );
}
上面的循环读取所有 230 万条记录并创建这个内存中的记录向量,只需要 1.5 秒(并且可能通过为向量预先分配空间来加快速度)。 (事实上,当我关闭关于实际将记录添加到向量中的部分时,仅查询的时间只有 0.95 秒。更神奇的是,当我使用带有回调函数的 sqlite3_exec() 时,而不是statement/step 方法,我可以在 0.55 秒内读取数据库中的所有“value”blob。)我发现如果我没有通过“value”字段对表进行索引,这些 select 语句大约需要 5 秒更长。 (不是我的主要问题,但我已经不明白为什么按“值”列索引会更快地查询表中的所有行从每一行获取“值”,但也许搜索引擎实际上可以使用存储在索引中的值,而不必从表本身读取值?)
另一个重要的评论是,当我在调试器中单步执行该循环时,我看到行的处理顺序出乎意料。我在想我会先得到 rowid 1,然后是 rowid 2,依此类推,因为我没有指定任何关于排序的内容,我只是要求它一次给我所有的行。但是,我发现我得到的第一个 rowid 在 600,000 的某个地方,然后 rowid 从那里跳来跳去。 所以这可能是因为它按“值”索引的顺序返回行,这是一些与物理记录/rowid 顺序无关的 b-tree 顺序?
无论如何,所以现在我在内存中有这个索引,并且在程序中的不同时间我想遍历那个表,检查每个条目的 _key,如果那个 _key 有某些属性,我想得到那个人的“价值”。所以我有一个循环:
sqlite3_stmt *stmt;
rc = sqlite3_prepare_v2( db, "select value from mytable where rowid = ?" ).c_str(), -1, &stmt, NULL );
for ( int i = 0 ; i < inMemoryIndex.size() ; i++ )
{
if ( MySpecialFunction( inMemoryIndex[ i ]._key ) )
{
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, inMemoryIndex[ i ]._rowId );
if ( sqlite3_step( stmt ) == SQLITE_ROW )
{
const void *v = sqlite3_column_blob( stmt, 0 );
DoWhatIWantWithV( v );
}
}
}
不幸的是(这里是我的主要问题),如果 230 万条记录中有大约 14,000 条通过了 MySpecialFunction() 测试,则该循环运行大约需要 1.6 秒。也就是说,读取 14000 条记录大约需要 1.6 秒,而读取全部 230 万条记录只需要 0.55 秒。
由于上面提到的奇怪的 rowid 排序,我确实尝试按 rowid 对 inMemoryIndex 进行排序。这使它在大约 1.3 秒而不是 1.6 秒内运行。
所以我的主要问题是:
我能够使用语句/步骤在 0.95 秒内选择 230 万行数据库中的每个“值”blob(事实上,如果我使用带有回调的 sqlite3_exec() 方法,我可以在 0.55 秒内完成)。
我在创建 inMemoryIndex 向量时遇到了麻烦,因为在大多数情况下,在任何给定时间,我只想要 230 万行中一小部分的记录,例如其中的 14,000 行。所以我想如果我知道这 14,000 个 rowid,我可以“只阅读那些行”。但是当我这样做时
"select value from mytable where rowid = ?"
语句迭代绑定到每个已知的rowid,耗时1.6秒,比读取数据库中的每一行要长得多。
所以:
(1) 我可以对这种方法进行一些小的更改(例如,一些其他索引、操作顺序等)可以加快速度吗?
(2) 这种做事方式是否存在根本性缺陷?
*(我应该评论说确实意识到像这样创建我自己的内存索引违背了我应该将查询计划留给 SQL 引擎本身的想法。我这样做是因为总的来说我的在给定时间决定我对哪些记录感兴趣的逻辑——如上面代码中的 MySpecialFunction() 所表达的——比我认为我在 SQL 逻辑中可以做的更复杂。我愿意接受我的想法需要重新考虑这一点。但现在我的问题是,从已知的 rowid 读取 14k 记录比读取所有 230 万条记录花费的时间要长得多,这似乎令人惊讶。)
更新/解决方案
这是我按照 pm100 的建议添加的代码,它将读取这 14,000 行的时间缩短到大约 0.19 秒。它仍然是读取完整 230 万条记录所需时间的 1/3 以上,但我会接受。
请注意,inMemoryIndex 已按 _rowId 排序。
sqlite3_intarray *intArrayPointer1;
sqlite3_intarray_create( db, "int_array_1", &intArrayPointer1 );
vector<sqlite3_int64> v;
for ( int i = 0 ; i < inMemoryIndex.size() ; i++ )
{
if ( MySpecialFunction( inMemoryIndex[ i ]._key ) )
{
v.push_back( inMemoryIndex[ i ]._rowId );
}
}
sqlite3_intarray_bind( intArrayPointer1, v.size(), &v[ 0 ], NULL );
sqlite3_stmt *stmt;
sqlite3_prepare_v2( db, "select value from mytable where rowid in int_array_1", -1, &stmt, NULL );
for ( ; sqlite3_step( stmt ) == SQLITE_ROW ; )
{
const void *blob = sqlite3_column_blob( stmt, 0 );
// ... work with "value" blob as you wish
}
【问题讨论】:
标签: c++ database performance sqlite