我有一个由6个数字组成的表作为主键
CREATE TABLE table1 ( num1 decimal, num2 int, num3 int, num4 bigint, num5 bigint, num6 bigint,
PRIMARY KEY (num1, num2, num3, num4, num5, num6))
我需要按排序顺序访问表格,而且我经常需要查询表格,以便按顺序查找下一个N个大数字及其相关数据。
所以我写的查询是这样的
SELECT * FROM table1 WHERE
num1 >? OR (
(num1 == ? AND num2 > ?) OR (
(num1 == ? AND num2 == ? AND num3 > ?) OR (
(num1 == ? AND num2 == ? AND num3 == ? AND num4 > ? OR (
(num1 == ? AND num2 == ? AND num3 == ? AND num4 == ? AND num5 > ?) OR (
(num1 == ? AND num2 == ? AND num3 == ?
AND num4 == ? AND num5 == ? AND num6 > ?)))))) ORDER BY num1, num2, num3, num4, num5, num6
LIMIT ?;
这是我能看到找到下一个最大密钥的最佳方式,而这确实按照索引的顺序查询....但查询需要几秒钟,这是我不喜欢的东西
有没有办法改善表现?这需要几秒钟才能在1000万行的表上执行,我需要它在100ms的数量级上执行更多。
查询计划:
"SEARCH TABLE table1 USING INDEX sqlite_autoindex_table1_1 (num1>?) (~250000 rows)"
"SEARCH TABLE table1 USING INDEX sqlite_autoindex_table1_1 (num1=? AND num2>?) (~2 rows)"
"SEARCH TABLE table1 USING INDEX sqlite_autoindex_table1_1 (num1=? AND num2=? AND num3>?) (~2 rows)"
"SEARCH TABLE table1 USING INDEX sqlite_autoindex_table1_1 (num1=? AND num2=? AND num3=? AND num4>?) (~2 rows)"
"SEARCH TABLE table1 USING INDEX sqlite_autoindex_table1_1 (num1=? AND num2=? AND num3=? AND num4=? AND num5>?) (~1 rows)"
"SEARCH TABLE table1 USING INDEX sqlite_autoindex_table1_1 (num1=? AND num2=? AND num3=? AND num4=? AND num5=? AND num6>?) (~1 rows)"
"USE TEMP B-TREE FOR ORDER BY"
编辑:
为什么这不可能?我真的希望得到INDEXED ORDER中的内容,与ORDER BY
关键字生成的顺序相同吗?
答案 0 :(得分:1)
如果这是解决问题的有效方法:您应该考虑使用单个代理键而不是六个部分的自然键。
从您自己的示例中可以看出,仅基于主键执行查找过于复杂。不需要仅考虑一列,而是需要执行多个索引查找。每个索引查找都涉及磁盘延迟,在您的情况下,它很容易控制整个处理时间。
只看到你发布的查询计划,在第一次查找之后,返回的行数已经减少到两个,并且查询剩余的索引与每个步骤后可以省略的行数相比是昂贵的(0-1行)。
因此,如果您只需要查询类型为integer的单个列作为主键,那么您应该会遇到一些显着的性能提升,正如您从SQLite docs所看到的那样:
SQLite中每个表的数据都存储为B树结构 包含每个表行的条目,使用rowid值作为 键。这意味着通过rowid检索或排序记录很快。 使用特定rowid或所有记录搜索记录 指定范围内的rowid大约是类似值的两倍 通过指定任何其他PRIMARY KEY或索引值进行搜索。
有一个例外,如果一个表的主键由a组成 单列,该列的声明类型为“INTEGER” 任何大写和小写的混合,然后该列成为别名 对于rowid。这样的列通常被称为“整数” 主键“。
此外,您的SQL查询将更简单,并且可以更轻松地维护。
答案 1 :(得分:1)
我认为在这种情况下查询优化器应该处理,但是在aqlite中它很简单,所以更改表格结构真的会更好@cyroxx写道。
其他想法:您也可以尝试以其他方式重写查询,并且可能是优化器将了解所需的内容。例如,您可以尝试:
SELECT * FROM table1 WHERE
num1 > 1 OR
( num1 = 1 AND ( num2 > 2 OR
( num2 = 2 AND ( num3 > 3 OR
( num3 = 3 AND ( num4 > 4 OR
( num4 = 4 AND ( num5 > 5 OR
( num5 = 5 AND num6 > 6)
)
)
)
)
)
)
)
)
可能会变得更好(或者更糟:))。
答案 2 :(得分:1)
与其他更复杂的RDBMS相反,sqlite有一个基于规则的查询优化器,这意味着执行计划主要取决于查询的编写方式(以及子句的顺序)。它使优化器非常可预测,如果您知道sqlite如何生成执行计划,您可以利用这种可预测性来解决您的问题。
第一个想法是要注意,诸如(num1>?)或(num1 =?和num2>?)之类的各种子句产生不相交的结果,并且这些结果自然地在彼此之间进行排序。如果查询被划分为子查询(每个子查询处理条件的一部分)产生排序结果,那么如果子查询以正确的顺序执行,则所有结果集的串联也会被排序。
例如,请考虑以下查询:
select * from table1 where num1=? and num2>? order by num1,num2
select * from table1 where num1>? order by num1,num2
这些查询生成的两个结果集是不相交的,第一个结果集的行总是在第二个结果集的行之前排序。
第二个想法是了解sqlite如何处理LIMIT子句。实际上,它在查询开始时声明了一个计数器,并在每个选定的行中递减并测试此计数器,因此它可以提前停止查询。
例如,请考虑以下查询:
.explain
explain select * from (
select * from table1 where num1=? and num2>?
union all
select * from table1 where num1>?
) limit 10;
sqlite将按查询中指定的顺序评估子查询。如果第一个子查询返回的行数超过10行,则甚至不会执行第二个子查询。 可以通过显示计划轻松检查:
addr opcode p1 p2 p3 p4 p5 comment
---- ------------- ---- ---- ---- ------------- -- -------------
0 Trace 0 0 0 00
1 Integer 10 1 0 00
2 Variable 1 2 2 00
3 Goto 0 44 0 00
4 OpenRead 3 3 0 keyinfo(6,BINARY,BINARY) 00
5 SCopy 2 4 0 00
6 IsNull 4 23 0 00
7 SCopy 3 5 0 00
8 IsNull 5 23 0 00
9 Affinity 4 2 0 cd 00
10 SeekGt 3 23 4 2 00
11 IdxGE 3 23 4 1 01
12 Column 3 1 6 00
13 IsNull 6 22 0 00
14 Column 3 0 7 00
15 Column 3 1 8 00
16 Column 3 2 9 00
17 Column 3 3 10 00
18 Column 3 4 11 00
19 Column 3 5 12 00
20 ResultRow 7 6 0 00
21 IfZero 1 23 -1 00
22 Next 3 11 0 00
23 Close 3 0 0 00
24 IfZero 1 43 0 00
25 Variable 3 13 1 00
26 OpenRead 4 3 0 keyinfo(6,BINARY,BINARY) 00
27 SCopy 13 14 0 00
28 IsNull 14 42 0 00
29 Affinity 14 1 0 c 00
30 SeekGt 4 42 14 1 00
31 Column 4 0 6 00
32 IsNull 6 41 0 00
33 Column 4 0 7 00
34 Column 4 1 8 00
35 Column 4 2 9 00
36 Column 4 3 10 00
37 Column 4 4 11 00
38 Column 4 5 12 00
39 ResultRow 7 6 0 00
40 IfZero 1 42 -1 00
41 Next 4 31 0 00
42 Close 4 0 0 00
43 Halt 0 0 0 00
44 Transaction 0 0 0 00
45 VerifyCookie 0 3 0 00
46 TableLock 0 2 0 table1 00
47 Goto 0 4 0 00
计数器被声明为步骤1,并在步骤21,24,40中递减/测试。
通过结合这两个评论,我们可以提出一个不太好的查询,但会产生一个有效的执行计划:
SELECT * FROM (
SELECT * FROM ( SELECT * FROM table1
WHERE num1 == ? AND num2 == ? AND num3 == ? AND num4 == ? AND num5 == ? AND num6 > ?
ORDER BY num1, num2, num3, num4, num5, num6 )
UNION ALL
SELECT * FROM ( SELECT * FROM table1
WHERE num1 == ? AND num2 == ? AND num3 == ? AND num4 == ? AND num5 > ?
ORDER BY num1, num2, num3, num4, num5, num6 )
UNION ALL
SELECT * FROM ( SELECT * FROM table1
WHERE num1 == ? AND num2 == ? AND num3 == ? AND num4 > ?
ORDER BY num1, num2, num3, num4, num5, num6 )
UNION ALL
SELECT * FROM ( SELECT * FROM table1
WHERE num1 == ? AND num2 == ? AND num3 > ?
ORDER BY num1, num2, num3, num4, num5, num6 )
UNION ALL
SELECT * FROM ( SELECT * FROM table1
WHERE num1 == ? AND num2 > ?
ORDER BY num1, num2, num3, num4, num5, num6 )
UNION ALL
SELECT * FROM ( SELECT * FROM table1
WHERE num1 > ?
ORDER BY num1, num2, num3, num4, num5, num6 )
) LIMIT ?;
请注意,因为外部查询中不需要“order by”子句,所以sqlite不需要执行所有子查询。所以当它具有正确的行数时它可以停止。子查询的顺序至关重要。
需要第二级内部子查询,因为在“all all”之前无法使用“order by”。它们被sqlite优化掉了,所以这不是问题。
在包含777K行的虚拟表上,初始查询成本为:
strace -c -eread,lseek sqlite3 toto.db < q1.sql
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
63.57 0.001586 0 18556 read
36.43 0.000909 0 18544 lseek
------ ----------- ----------- --------- --------- ----------------
100.00 0.002495 37100 total
虽然我只花费:
strace -c -eread,lseek sqlite3 toto.db < q3.sql
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
-nan 0.000000 0 18 read
-nan 0.000000 0 8 lseek
------ ----------- ----------- --------- --------- ----------------
100.00 0.000000 26 total