SQLite:大表上COUNT慢

时间:2012-01-24 14:54:56

标签: sql database performance sqlite

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

由于我还没有收到有用的答案而且我做了一些进一步的测试,我编辑了我的问题以结合我的新发现。

我有两张桌子:

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

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

Table1还有两个索引:

CREATE INDEX IDX_Table1_Status ON Table1 (Status ASC, Key ASC)
CREATE INDEX IDX_Table1_Selection ON Table1 (Selection ASC, Key ASC)

“状态”是必填字段,但只有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)

正如您所看到的,计数非常慢,但正常选择很快(第二个除外,需要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)

为什么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)

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

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

CREATE INDEX IDX_Table1_Key ON Table1 (Key ASC)

然后我再次运行测试:

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)

正如您所看到的,索引帮助计数(*),但没有计数(键)。

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

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

然后我再次运行测试:

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)

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

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

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

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

谢谢,

马克

7 个答案:

答案 0 :(得分:24)

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

SQLite始终对count(*)执行全表扫描。它
没有保留表格上的元信息来加快这一点 加快。

不保留元信息是一种刻意的设计 决策。如果每个表存储一个计数(或更好,每个 btree的节点存储了一个计数)然后更多更新
必须在每个INSERT或DELETE上发生。这
即使在常见的情况下,也会减慢INSERT和DELETE的速度 计数(*)速度不重要的情况。

如果你真的需要快速COUNT,那么你可以创建
INSERT和DELETE上的触发器,用于更新正在运行的 计入一个单独的表,然后查询单独的
表找到最新的计数。

当然,如果你是这样的话,不值得保留一个完整的行数 需要依赖于WHERE子句的COUNT(即WHERE field1> 0和field2< 1000000000)。

答案 1 :(得分:23)

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

SELECT MAX(_ROWID_) FROM "table" LIMIT 1;

将避免全表扫描。请注意_ROWID_ is a SQLite identifier

答案 2 :(得分:3)

不计算星星,统计记录!或者用其他语言,永远不要发出

SELECT COUNT(*)FROM tablename;

使用

SELECT COUNT(ROWID)FROM tablename;

调用EXPLAIN QUERY PLAN以查看差异。确保您有一个包含WHERE子句中提到的所有列的索引。

答案 3 :(得分:0)

关于列约束,SQLite将声明为INTEGER PRIMARY KEY的列映射到内部行id(后者允许进行许多内部优化)。从理论上讲,它可以对单独声明的主键约束执行相同的操作,但在实践中似乎不会这样做,至少对于正在使用的SQLite版本。 (System.Data.SQLite 1.0.74.0对应于核心SQLite 3.7.7.1。您可能希望尝试使用1.0.79.0重新检查您的数字;您不需要更改数据库来执行此操作,只需要库。)

答案 4 :(得分:0)

快速查询的输出都以文本“QP:SEARCH”开头。虽然慢查询的开头是文本“QP:SCAN”,这表明sqlite正在执行整个表的扫描以生成计数。

谷歌搜索“sqlite表扫描计数”找到the following,这表明使用全表扫描来检索计数正是sqlite的工作方式,因此可能是不可避免的。

作为一种解决方法,并且鉴于该状态只有八个值,我想知道您是否可以使用如下查询快速计数?

选择1,其中status = 1 联盟 选择1,其中status = 2 ...

然后计算结果中的行数。这显然很难看,但如果它说服sqlite将查询作为搜索而不是扫描运行,它可能会有效。每次返回“1”的想法是避免返回实际数据的开销。

答案 5 :(得分:0)

这是提高查询性能的潜在解决方法。从上下文来看,听起来你的查询大约需要一分半钟才能运行。

假设你有一个date_created列(或者可以添加一个),每天午夜(比如凌晨00:05)在后台运行一个查询并将值保存在某个地方以及计算的last_updated日期(我会稍微再说一遍。

然后,针对date_created列(带索引)运行,可以通过执行SELECT COUNT(*)FROM TABLE WHERE date_updated>等查询来避免全表扫描。 “[今日] 00:00:05”。

将该查询的计数值添加到您的持久值,并且您的计数通常是准确的。

唯一的问题是从早上12:05到凌晨12:07(运行总计数查询的持续时间),您有一个竞争条件,您可以检查全表扫描计数的last_updated值()。如果是> 24小时后,您的增量计数查询需要提取一整天的计数加上今天过去的时间。如果它是< 24小时后,您的增量计数查询需要提取部分日计数(此时间已过去)。

答案 6 :(得分:0)

我遇到了同样的问题,在我的情况下,VACUUM命令有帮助。在数据库COUNT(*)执行后,速度提高了近100倍。但是,命令本身在我的数据库中需要几分钟(20百万条记录)。我在主窗口销毁后退出软件时运行VACUUM解决了这个问题,因此延迟不会给用户带来问题。