我在使用 SELECT COUNT(*) 对大型表进行 SQLite 时遇到性能问题。
由于我还没有收到可用的答案并且我做了一些进一步的测试,所以我编辑了我的问题以纳入我的新发现。
我有 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))
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)
“状态”是必填字段,但只有 6 个不同的值,“选择”不是必需的,只有大约 150 万个不同于 null 的值,只有大约 60 万个不同的值。
我对两个表都做了一些测试,你可以看到下面的时间,我为每个请求 (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上使用auto-index的时候还是要花很多时间?
我在 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 字段上创建了一个额外的索引,它应该对应于自动索引。我在原始数据库上做了这个,没有分析数据。创建这个索引用了 23 多分钟(记住,这是在 U 盘上)。
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)
如您所见,索引对 count(*) 有帮助,但对 count(Key) 没有帮助。
最后,我使用列约束而不是表约束创建了表:
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 的测试。
谢谢,
马克
最佳答案
如果您还没有DELETE
任何记录,做:
SELECT MAX(_ROWID_) FROM "table" LIMIT 1;
将避免全表扫描。
关于SQLite:大表上的 COUNT 速度慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8988915/