【问题标题】:Sqlite Query Optimization (using Limit and Offset)Sqlite 查询优化(使用 Limit 和 Offset)
【发布时间】:2021-01-10 05:22:28
【问题描述】:

以下是我用于从具有数百万条记录的数据库中获取固定数量记录的查询:-

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 条记录)获取记录。但正如我所提到的,随着偏移量的增加,它变得很慢。所以,我的最终目标是我想从数据库中读取所有记录。但由于我无法在一次执行中获取所有记录,因此我需要一些其他有效的方法来实现这一点。

【问题讨论】:

    标签: sqlite


    【解决方案1】:

    正如 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!
    

    【讨论】:

      【解决方案2】:

      通过执行偏移量为 95000 的查询,将处理所有之前的 95000 条记录。你应该在表上做一些索引,然后用它来选择记录。

      【讨论】:

      • 对不起,我不明白。通过制作索引意味着我应该做什么?
      • 在对你很重要的字段上创建一个索引,然后确保该字段在查询中使用:'select * from myTable order by index_field LIMIT 100 OFFSET 95000' SQLite 然后处理索引而不是表,女巫要快得多!
      • 哦,好的。看,我的数据库有 6 列,其中一列是“INTEGER PRIMARY KEY”。据我所知,索引是为声明为主键的列自动创建的。那么,对于“按 index_field 排序”,如果我提及主键列名代替“index_field”而不为该字段手动创建索引,是否可以?
      • 是的!那你应该没事吧!
      • 谢谢。实际上,到目前为止,我从未在我的 Sqls 中使用过索引,这就是为什么要避免使用它们。明天我将执行您的查询,如果遇到任何问题,我会回复您。
      【解决方案3】:

      正如@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)
      

      这会加快速度,特别是如果您有“胖”行(例如,包含 blob)。

      如果维护记录顺序很重要(通常不重要),您需要先对索引进行排序:

      select * from Table where rowid in (
        select rowid from Table order by rowid limit %size offset %start)
      

      【讨论】:

      • 我试过了,非常惊讶地发现它有效! SELECT rowid FROM Table LIMIT 30 OFFSET 100000 的子查询在我的测试数据库上确实很快。但这似乎是内部的东西而且非常脆弱:运行EXPLAIN QUERY PLAN 我发现它使用了一个看似不相关的复合索引,当我删除这个索引时返回到一个缓慢的全表扫描,即使rowid 上的索引仍然存在(一如既往)。将ORDER BY rowid 放入内部查询也会导致全表扫描,尽管这可能是它已经使用的顺序。
      • 最奇怪的是,在内部查询中将SELECT rowid 更改为SELECT *(在这种情况下,您不再需要外部查询)会导致查询使用全表扫描,并且速度很慢明显下降,即使我希望这样的变化不会改变选择的索引。总的来说,我认为在索引的递增计数器列上用 WHERE 子句替换 LIMITOFFSET 会更安全,正如 user318750 在他们的回答中所建议的那样。
      • @ArthurTacca 我回答的出发点正是要摆脱一个有连续计数器的假设。根据我的经验,几乎没有任何应用程序可以确保存在可计数的计数器。 id 被删除后不被重用这一事实通常被视为一个可取的特性。
      【解决方案4】:
      select * from data where rowid = (select rowid from data limit 1 offset 999999);
      

      【讨论】:

      • 我很惊讶我的问题只有 8 年前才有好的答案。如果您唯一的 id 列是基于文本的列,那将是一个很好的答案。非常感谢伙计。
      • 值得注意的是,您可以在此处按子查询中的索引列排序,它仍然很高效!
      【解决方案5】:

      使用 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);
        });
      

      【讨论】:

        猜你喜欢
        • 2011-03-20
        • 1970-01-01
        • 1970-01-01
        • 2012-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-02
        • 2021-05-17
        相关资源
        最近更新 更多