SQLite:大表上COUNT慢

Mar*_*arc 31 sql database sqlite performance

我在SQLite中遇到性能问题,在大型表上使用SELECT COUNT(*).

由于我还没有收到有用的答案,我做了一些进一步的测试,我编辑了我的问题以纳入我的新发现.

我有2张桌子:

CREATE TABLE Table1 (
Key INTEGER NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL,
CONSTRAINT PK_Table1 PRIMARY KEY (Key ASC))

CREATE Table2 (
Key INTEGER NOT NULL,
Key2 INTEGER NOT NULL,
... a few other fields ...,
CONSTRAINT PK_Table2 PRIMARY KEY (Key ASC, Key2 ASC))
Run Code Online (Sandbox Code Playgroud)

Table1有大约800万条记录,Table2有大约5100万条记录,数据库文件超过5GB.

Table1还有2个索引:

CREATE INDEX IDX_Table1_Status ON Table1 (Status ASC, Key ASC)
CREATE INDEX IDX_Table1_Selection ON Table1 (Selection ASC, Key ASC)
Run Code Online (Sandbox Code Playgroud)

"状态"是必填字段,但只有6个不同的值,"选择"不是必需的,只有大约150万个值与null不同,只有大约600k个不同的值.

我在两个表上做了一些测试,你可以看到下面的时间,我为每个请求(QP)添加了"解释查询计划".我将数据库文件放在USB记忆棒上,这样我就可以在每次测试后将其删除,并获得可靠的结果,而不会干扰磁盘缓存.有些请求在USB上更快(我想由于缺少搜索时间),但有些请求更慢(表扫描).

SELECT COUNT(*) FROM Table1
    Time: 105 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
    Time: 153 sec
    QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Key = 5123456
    Time: 5 ms
    QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid=?) (~1 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 16 sec
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
SELECT * FROM Table1 WHERE Selection = 'SomeValue' AND Key > 5123456 LIMIT 1
    Time: 9 ms
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Selection (Selection=?) (~3 rows)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,计数非常慢,但正常选择很快(第二个除外,需要16秒).

表2也是如此:

SELECT COUNT(*) FROM Table2
    Time: 528 sec
    QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~1000000 rows)
SELECT COUNT(Key) FROM Table2
    Time: 249 sec
    QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table2 WHERE Key = 5123456 AND Key2 = 0
    Time: 7 ms
    QP: SEARCH TABLE Table2 USING INDEX sqlite_autoindex_Table2_1 (Key=? AND Key2=?) (~1 rows)
Run Code Online (Sandbox Code Playgroud)

为什么SQLite没有在Table1上的主键上使用自动创建的索引?为什么,当他在Table2上使用自动索引时,仍然需要花费很多时间?

我在SQL Server 2008 R2上创建了具有相同内容和索引的相同表,并且计数几乎是即时的.

下面的评论之一建议在数据库上执行ANALYZE.我做了,花了11分钟才完成.之后,我再次运行了一些测试:

SELECT COUNT(*) FROM Table1
    Time: 104 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~7848023 rows)
SELECT COUNT(Key) FROM Table1
    Time: 151 sec
    QP: SCAN TABLE Table1 (~7848023 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 5 ms
    QP: SEARCH TABLE Table1 USING INTEGER PRIMARY KEY (rowid>?) (~196200 rows)
SELECT COUNT(*) FROM Table2
    Time: 529 sec
    QP: SCAN TABLE Table2 USING COVERING INDEX sqlite_autoindex_Table2_1(~51152542 rows)
SELECT COUNT(Key) FROM Table2
    Time: 249 sec
    QP: SCAN TABLE Table2 (~51152542 rows)
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,查询花费的时间相同(除了查询计划现在显示的是实际行数),现在只有较慢的选择也很快.

接下来,我在Table1的Key字段上创建了dan extra index,它应该与auto-index对应.我在原始数据库上做了这个,没有ANALYZE数据.创建此索引花了超过23分钟(请记住,这是在USB记忆棒上).

CREATE INDEX IDX_Table1_Key ON Table1 (Key ASC)
Run Code Online (Sandbox Code Playgroud)

然后我再次运行测试:

SELECT COUNT(*) FROM Table1
    Time: 4 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Key(~1000000 rows)
SELECT COUNT(Key) FROM Table1
    Time: 167 sec
    QP: SCAN TABLE Table2 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 17 sec
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
Run Code Online (Sandbox Code Playgroud)

如您所见,索引帮助计数(*),但不计算计数(键).

最后,我使用列约束而不是表约束创建了表:

CREATE TABLE Table1 (
Key INTEGER PRIMARY KEY ASC NOT NULL,
... several other fields ...,
Status CHAR(1) NOT NULL,
Selection VARCHAR NULL)
Run Code Online (Sandbox Code Playgroud)

然后我再次运行测试:

SELECT COUNT(*) FROM Table1
    Time: 6 sec
    QP: SCAN TABLE Table1 USING COVERING INDEX IDX_Table1_Selection(~1000000 rows)
SELECT COUNT(Key) FROM Table1
    Time: 28 sec
    QP: SCAN TABLE Table1 (~1000000 rows)
SELECT * FROM Table1 WHERE Status = 73 AND Key > 5123456 LIMIT 1
    Time: 10 sec
    QP: SEARCH TABLE Table1 USING INDEX IDX_Table1_Status (Status=?) (~3 rows)
Run Code Online (Sandbox Code Playgroud)

虽然查询计划是相同的,但时间要好得多.为什么是这样 ?

问题是ALTER TABLE不允许转换现有的表,我有很多现有的数据库,我无法转换为这种形式.此外,使用列约束而不是表约束对Table2不起作用.

有谁知道我做错了什么以及如何解决这个问题?

我使用System.Data.SQLite版本1.0.74.0创建表并运行我使用SQLiteSpy 1.9.1的测试.

谢谢,

Ali*_*xel 28

如果您还没有DELETE任何记录,请执行以下操作:

SELECT MAX(_ROWID_) FROM "table" LIMIT 1;
Run Code Online (Sandbox Code Playgroud)

将避免全表扫描.请注意,这_ROWID_是一个SQLite标识符.

  • 为什么需要“LIMIT 1”?只有一个最大值。另外,最好选择“COALESCE(MAX(_ROWID_), 0)”,否则对于空表不会返回任何内容。 (3认同)
  • 这很好,但要记住Alix已经说过的内容 - 即使你删除了这个表中的单个记录 - 你也会得到不正确的结果(因为_ROWID_是一个不断递增的记录ID,'删除'不会导致_ROWID_减少). (2认同)
  • 还应该注意的是,只有当您有代理键并且如前所述没有删除任何行时,使用“MAX(_ROWID_)”才有意义。这两个限制意味着这不是获取行计数的可靠方法,无论它有多快。 (2认同)

小智 26

来自http://old.nabble.com/count(*)-slow-td869876.html

SQLite始终对count(*)执行全表扫描.它
不会在表上保留元信息以加快此
过程.

不保留元信息是一个深思熟虑的设计
决策.如果每个表都存储了一个计数(或者更好,
btree的每个节点都存储了一个计数),那么
每次INSERT或DELETE都需要进行更多的更新.
即使在
count(*)速度不重要的常见情况下,这也会减慢INSERT和DELETE的速度.

如果您确实需要快速COUNT,那么您可以
在INSERT和DELETE上创建一个触发器,
在单独的表中更新运行计数,然后查询该单独的
表以查找最新计数.

当然,如果
需要依赖于WHERE子句的COUNT(即WHERE field1> 0和field2 <1000000000),则不值得保持FULL行计数.


Ste*_*ngs 0

这可能没有多大帮助,但您可以运行ANALYZE命令来重建有关数据库的统计信息。尝试运行“ ANALYZE;”来重建整个数据库的统计信息,然后再次运行查询,看看它是否更快。