【发布时间】:2012-02-17 19:44:34
【问题描述】:
我在 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))
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 秒)。
Table2 也是如此:
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 上创建了具有相同内容和索引的相同表,并且计数几乎是瞬时的。
下面的其中一个 cmets 建议对数据库执行 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 多分钟(请记住,这是在 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)
如您所见,索引对 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 的测试。
谢谢,
马克
【问题讨论】:
-
如果您在使用 SQLite 时遇到性能问题,解决方案通常是升级到更大的数据库服务器(我推荐 Postgres 而不是 MS SQL)。
-
我没有任何其他性能问题,所有其他选择都很快(并使用正确的索引),插入和更新很快,只是计数让我感到困扰。
-
@Borealid:Postgres 还对没有 WHERE 子句的 COUNT 查询使用全表扫描,因此切换到 Postgres 不会为您带来该查询的任何性能优势。
-
@limscoder 但是您可以执行触发器来维护信息。或者通过让它们在 RAM 中的热计数来节省您在正常查询上的时间。此外,InnoDB 在这方面并没有快多少,而 MyISAM 没有事务......
-
@borealis SQLite 也支持触发器,因此跟踪计数的机制是相同的。
标签: sql database performance sqlite