有效地检索rowid的SQLite行

时间:2015-11-04 01:15:51

标签: c++ database performance sqlite

我正在使用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()时,更令人惊讶的是,而不是语句/步骤方法,我可以在0.55秒内读取数据库中的所有&#34;值&#34; blob。)我发现如果我在表上没有&#34;值&#34的索引;字段,这些选择语句大约需要5秒钟。 (不是我的主要问题,但我已经不明白为什么索引为&#34;值&#34;列会更快地查询表格中的所有行从每一行获得&#34;值&#34;但也许搜索引擎实际上可以使用存储在索引中的值而不必从表本身读取值?)

另一个重要的评论是,当我在调试器中逐步执行该循环时,我看到行以意外的顺序处理。我以为我会首先获得rowid 1,然后获得rowid 2,依此类推,因为我没有指定任何关于排序的内容,我只是要求它一次一个地给我一行。然而,我发现我得到的第一个rowid是在600,000的某个地方,然后rowid从那里跳来跳去。 也许是因为它按照&#34;值&#34;的顺序返回行。 index,这是一些与物理记录/ rowid顺序无关的b-tree顺序?

无论如何,现在我在内存中有这个索引,并且在程序的不同时间我想要遍历该表,并检查每个条目的_key,如果_key具有某些属性,我想得到&#34;值&#34;对那个人所以我有一个循环:

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秒。也就是说,读取14,000条记录需要大约1.6秒,而读取所有230万条记录只需要0.55秒。

由于上面提到的奇怪的rowid排序,我确实尝试用rowid对inMemoryIndex进行排序。这使它在大约1.3秒内运行而不是1.6。

所以我的主要问题是:

我可以使用语句/步骤选择每个&#34;值&#34; blob在230万行数据库中0.95秒(实际上如果我使用sqlite3_exec()方法进行回调,我可以在0.55秒内完成)。

我遇到了创建inMemoryIndex向量的麻烦,因为在大多数情况下,在任何给定时间我只需要记录230万行的一小部分,例如14,000行。所以我想如果我知道这些14,000个辫子我可以&#34;只读这些行&#34;。但是当我用

这样做的时候
"select value from mytable where rowid = ?"

语句迭代地绑定到每个已知的rowid,它需要1.6秒,比读取数据库中的每个行要长得多。

所以:

(1)我可以对这种方法做出一些小的改变(例如,其他一些指数,操作顺序等)可以加快它的速度吗?

(2)这种做事方式是否存在根本缺陷?

*(我应该发表评论,确实意识到创建我自己的内存索引是违背我应该将查询规划留给SQL引擎本身的想法。我这样做是因为在一般来说,我决定在给定时间对我感兴趣的记录的逻辑 - 如上面代码中的MySpecialFunction()所表示的那样 - 比我认为我在SQL逻辑中可以做的更复杂。我&#39; m开放的想法,我需要重新考虑这一点。但是现在我的问题只是这个事实似乎令人惊讶的是,从已知的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
}

1 个答案:

答案 0 :(得分:2)

有一个代码插件,它使用虚拟表来完成您想要的任务。

https://www.sqlite.org/src/artifact/9dc57417fb65bc78     https://www.sqlite.org/src/artifact/870124b95ec4c645