有效地按行宽检索SQLite行

retrieve SQLite rows by rowid efficiently

本文关键字:SQLite 检索 有效地      更新时间:2023-10-16

我正在使用C接口到SQLite,并且有一些关于rowwid字段的基本问题,以及如何有效地从具有已知rowid的任意行集合中检索数据。实际上我有几个相关的问题,所以我会用粗体标出。但我的主要问题在最后。

I have a table:

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 );

我知道行索引是不必要的。我看到SQLite花了0秒来"创建"rowwid索引,我相信这是因为rowwid总是表上隐式存在的"索引",因为表(通常)是按rowwid顺序存储的。

在任何情况下,我想做的是能够快速地从这个表中检索任意行集,按行。我所做的是在内存中创建一个记录列表:

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()和一个回调函数,而不是声明/步骤方法,我能读懂所有的"价值"数据库中的blob 0.55秒)。我发现,如果我没有一个索引表的"价值"字段,这些select语句需要大约5秒。(不是我的主要问题,但我已经不明白为什么索引的"值"列会使它更快地查询表所有行从每行获取"值",但也许搜索引擎实际上可以使用存储在索引中的值,而不是从表本身读取值?)

另一个重要的注释是,当我在调试器中逐步执行该循环时,我看到行以意想不到的顺序处理。我想我应该先得到row wid 1,然后是row wid 2,以此类推,因为我没有指定任何关于排序的东西,我只是让它一次一个地给我所有的行。然而,我发现我得到的第一个队列是在60万左右,然后队列从那里跳来跳去。所以也许这是因为它以"值"索引的顺序返回行,这是一些与物理记录/行顺序无关的b-树顺序?

不管怎样,现在我在内存中有了这个索引,在程序的不同时间里,我想遍历这个表,检查每个条目的_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秒。

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

所以我的主要问题是:

我能够使用语句/步骤在0.95秒内选择230万行数据库中的每个"值"blob(事实上,如果我使用sqlite3_exec()方法与回调,我可以在0.55秒内完成)。

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

"select value from mytable where 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
}

有一个代码插件,它使用一个虚拟表来做你想做的事情。

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