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标识符.
小智 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行计数.