以下是我用于从具有数百万条记录的数据库中获取固定数量记录的查询:-
select * from myTable LIMIT 100 OFFSET 0
我观察到,如果偏移量非常高(例如 90000),那么执行查询需要更多时间。以下是具有不同偏移量的 2 个查询之间的时间差:
select * from myTable LIMIT 100 OFFSET 0 //Execution Time is less than 1sec
select * from myTable LIMIT 100 OFFSET 95000 //Execution Time is almost 15secs
有人可以建议我如何优化这个查询吗?我的意思是,对于我希望从任何 OFFSET 检索的任意数量的记录,查询执行时间应该相同且快速。
新增:- 实际情况是我有一个超过 100 万条记录的数据库。但由于它是嵌入式设备,我无法执行“select * from myTable”,然后从查询中获取所有记录。我的设备崩溃了。相反,我所做的是按照上面提到的查询,不断批量获取记录(批量大小 = 100 或 1000 条记录)。但正如我提到的,随着偏移量的增加,它会变得很慢。所以,我的最终目标是我想从数据库中读取所有记录。但由于我无法在一次执行中获取所有记录,因此我需要其他一些有效的方法来实现这一点。
正如JvdBerg所说,LIMIT/OFFSET 中不使用索引。 简单地添加“ORDER BY indexed_field”也无济于事。
为了加快分页速度,您应该避免使用 LIMIT/OFFSET 并使用 WHERE 子句。例如,如果您的主键字段名为“id”并且没有间隙,则上面的代码可以这样重写:
SELECT * FROM myTable WHERE id>=0 AND id<100 //very fast!
SELECT * FROM myTable WHERE id>=95000 AND id<95100 //as fast as previous line!
正如@user318750所说,如果你知道你有一个连续的索引,你可以简单地使用
select * from Table where index >= %start and index < %(start+size)
但是,这种情况很少见。如果您不想依赖该假设,请使用子查询,例如使用
rowid
,它始终被索引,
select * from Table where rowid in (
select rowid from Table limit %size offset %start)
这会加快速度,特别是如果您有“胖”行(例如包含斑点)。
如果维护记录顺序很重要(通常不重要),则需要首先对索引进行排序:
select * from Table where rowid in (
select rowid from Table order by rowid limit %size offset %start)
通过执行偏移量为 95000 的查询,将处理所有之前的 95000 条记录。您应该在表上创建一些索引,并使用它来选择记录。
select * from data where rowid = (select rowid from data limit 1 offset 999999);
使用 SQLite,您不需要在一个大的胖数组中一次返回所有行,您可以为每一行回调。这样,您就可以在结果出现时对其进行处理,这应该可以解决崩溃和性能问题。
我猜你没有使用 C,因为你已经在使用回调了,但这种技术应该可以在任何其他语言中使用。
Javascript 示例(来自:https://www.npmjs.com/package/sqlite3)
db.each("SELECT rowid AS id, info FROM lorem", function(err, row) {
console.log(row.id + ": " + row.info);
});
偏移、限制问题有两种情况(例如myOffset、myLimit)
1-偏移量可以基于数字、唯一且连续的字段
如果该字段是“id”,那么这两个过滤器中的任何一个都可以完成这项工作
... WHERE id >= myOffset LIMIT myLimit
or
... WHERE id >= myOffset AND id < myOffset + myLimit
对于不包含显式数字唯一字段的表,将创建 rowid 由 sqlite 自动生成 - 是此类 id 的完美候选者
但即使如此,ID 中也可能存在间隙,例如当记录被删除时。 因此,偏移量限制可能会返回重复的记录或小于所提供限制的数字
2-偏移量不能直接基于表的任何列
在这种情况下,除了使用 OFFSET LIMIT 并接受大偏移量的成本之外,没有其他方法。 实际上sqlite无法对此进行优化,因为它必须遍历所有以前的记录来对它们进行计数。
或多或少静态表的解决方案:
如果所讨论的大桌子没有太大变化或者我们可以负担额外的时间 每次表更改时执行以下查询
DROP TABLE tmpOffetMap ;
CREATE TABLE tmpOffetMap SELECT rowid AS tableRowid FROM table ;
这将创建一个与巨大的表“表”一样多记录的表,但只有两列:rowid和tableRowid
然后选择“table”作为 myOffset 和 myLimit 看起来像
SELECT * FROM table WHERE rowid IN (SELECT tableRowid FROM tmpOffetMap WHERE rowid >= myOffset AND rowid < myOffset + myLimit)
请注意,“rowid”是不同的列,并且在表“table”和“tmpOffsetMap”中具有不同的值