【问题标题】:Counting rows in a sqlite db计算 sqlite 数据库中的行数
【发布时间】:2011-11-24 11:23:54
【问题描述】:

我在运行 Linux 的 ARM 嵌入式平台上有一个 sqlite 数据库,但资源有限。存储设备是 microSD 卡。 Sqlite 版本是 3.7.7.1。访问 sqlite 的应用程序是用 C++ 编写的。

我想定期知道几个表中的行数。我目前使用

select count(*) from TABLENAME;

获取此信息。我遇到了性能问题:当表大小达到某个点(~200K 行)时,每次检查表大小时都会有很多系统和 iowait 负载。

当我写这篇文章时,我虽然查找表中的行数会很快,因为它可能存储在某个地方。但是现在我怀疑 sqlite 实际上会查看所有行,当我通过数据不再适合磁盘缓存的点时,我会得到很多 io 负载。这将大致适合 db 大小和可用内存。

谁能告诉我 sqlite 是否以我怀疑的方式行事?

有没有什么方法可以在不产生这么多负载的情况下获取表行数?

编辑:plaes 询问了表格布局:

CREATE TABLE %s (timestamp INTEGER PRIMARY KEY, offset INTEGER, value NUMERIC);

【问题讨论】:

  • 可能这不是非常有效,但您可以通过添加触发器来修改您的数据库,这些触发器为每个表增加/减少一个全局行计数器。见sqlite.org/lang_createtrigger.html。
  • 如果添加自增整数索引会发生什么?
  • @plaes:我查看了各种查询计划。计数查询似乎想要查看所有行(0|0|0|SCAN TABLE channel_17(~232666 行))。当我使用自动增量 rowid 创建一个测试表时,这仍然是正确的。 Sqlite Doku 说:“[...] PRIMARY KEY 约束是通过在数据库中创建索引来实现的”,因此缺少索引应该不是问题。
  • 您能否对下表进行相同的“分析”:CREATE TABLE %s (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, offset INTEGER, value NUMERIC);? (将插入NULL插入id列时)
  • @plaes:我是为CREATE TABLE %s (rowid INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER UNIQUE, offset INTEGER, value NUMERIC); 做的,这与以前的问题相同。

标签: linux performance sqlite


【解决方案1】:

这个表有integer 索引吗?如果没有,那就加一个。否则它必须扫描整个表来计算项目。

这是从实现COUNT()解析和执行的SQLite代码中摘录的cmets:

    /* If isSimpleCount() returns a pointer to a Table structure, then
    ** the SQL statement is of the form:
    **
    **   SELECT count(*) FROM <tbl>
    **
    ** where the Table structure returned represents table <tbl>.
    **
    ** This statement is so common that it is optimized specially. The
    ** OP_Count instruction is executed either on the intkey table that
    ** contains the data for table <tbl> or on one of its indexes. It
    ** is better to execute the op on an index, as indexes are almost
    ** always spread across less pages than their corresponding tables.
    */
    [...]
    /* Search for the index that has the least amount of columns. If
    ** there is such an index, and it has less columns than the table
    ** does, then we can assume that it consumes less space on disk and
    ** will therefore be cheaper to scan to determine the query result.
    ** In this case set iRoot to the root page number of the index b-tree
    ** and pKeyInfo to the KeyInfo structure required to navigate the
    ** index.
    **
    ** (2011-04-15) Do not do a full scan of an unordered index.

此外,您可以通过EXPLAIN QUERY PLANget more information 查询您的查询。

【讨论】:

    【解决方案2】:

    从我收集的所有信息来看,count() 显然确实需要扫描表。正如 plaes 所指出的,如果在整数索引列上完成计数,这会更快,但仍然需要扫描索引。

    我现在所做的是将行数存储在某处,并在我用来执行插入和删除的相同事务中手动增加/减少它以保持一致。

    【讨论】:

    • 您是如何增加计数器的,使用触发器还是手动?
    • @hochl:我手动增加它们。我觉得在我的情况下我没有足够的 sqlite/触发器经验来足够安全地做到这一点。
    【解决方案3】:

    这里有 2 种可能的表行计数解决方法(带有警告),不会导致表/索引扫描:

    1. 注意对于可以使用 INTEGER PRIMARY KEY AUTOINCREMENT 作为主键的表,可以从 sqlite_sequence sqlite 元表中获取计数:

      从 sqlite_sequence 中选择名称、序列

    seq 将包含最后一个 id 或下一个 id(我认为是最后一个但不确定)。

    1. “从表中选择 max(pkid)”,它可能会执行索引搜索而不是扫描(并且也只会对没有删除的表准确)。

    知道这一点后,如果您的用例包括对可以使用 AUTOINCREMENT 的表的唯一删除,则可以混合使用基于触发器的解决方案,并且只计算已删除的行(可以说,这比对大多数插入计数要少记账)情景)。但是,如果您两次插入和删除同一行,这也将不起作用。

    【讨论】:

      猜你喜欢
      • 2011-08-24
      • 2021-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多