【问题标题】:Slow SQL Query on indexed columns, multi-column number索引列上的慢速 SQL 查询,多列数
【发布时间】:2012-08-08 05:30:52
【问题描述】:

我有一个由 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 万行的表上执行,我需要它在 100 毫秒左右执行更多。

查询计划:

"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 关键字生成的顺序相同?

【问题讨论】:

  • 删除了我的答案,因为我提供的信息基于错误的数据库引擎...抱歉。
  • 每列是否有单独的索引
  • 这不应该是必需的,它使用索引它只是尝试在临时 b 树中对结果进行排序。 select * from table1 where num1 > ? AND num6 == 1 order by num1, num2, num3, num4, num5, num6 limit 1000 在 25 毫秒内执行,这些列的其他组合也是如此

标签: sql performance sqlite indexing query-optimization


【解决方案1】:

与其他更复杂的 RDBMS 不同,sqlite 有一个基于规则的查询优化器,这意味着执行计划主要取决于查询的编写方式(以及子句的顺序)。它使优化器非常可预测,如果您知道 sqlite 如何生成执行计划,您可以利用这种可预测性来解决您的问题。

第一个想法是注意各种子句,如 (num1>?) 或 (num1=? and 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执行所有子查询。所以它可以在它有正确的行数时停止。子查询的顺序很关键。

需要第二级内部子查询,因为不能在“union 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

【讨论】:

  • 这似乎有效,但它远没有你看到的那么快(但对我来说仍然足够快)。不过,我很感激。我知道必须有某种方法可以让 sqlite 查询优化器提交。
  • 通过将您的答案与 mvf 的答案相结合,您可以在解释查询计划中将其减少到 2 个数据库访问。
  • 对不起,我不明白你的意思。要么你有一个大的昂贵的索引范围扫描,要么你有(最多)6个便宜的索引范围扫描。我不认为这两种策略可以结合起来。查询计划中的步数不会提高这里查询的响应时间。
  • 如果你将 SELECT * FROM ( SELECT * FROM table1 WHERE num1 > ? ORDER BY num1, num2, num3, num4, num5, num6 ) 与 select * from ( select * from table1 where ( num1 = ? AND ( num2 > ? OR ( num2 = ? AND ( num3 > ? OR ( num3 = ? AND ( num4 > ? OR ( num4 = ? AND ( num5 > ? OR( num5 = ? AND num6 > ?)))) )))))) 对我来说似乎更快。
  • 好的,我现在知道了。好吧,只有当您的第一列具有足够的判别力(这可能完全适合您的情况)时,它才会有效。我相信根据数据分布,在某些病理情况下,它的表现几乎与原始查询一样糟糕。我认为我的提议不会发生这种情况,因为范围扫描始终限于最多扫描 n 行(n 是 LIMIT 参数)。
【解决方案2】:

如果这是对您的问题的有效解决方案:您应该真正考虑使用单个代理键而不是六部分自然键。

从您自己的示例中可以看出,仅基于主键进行查找过于复杂。需要执行多个索引查找,而不是只考虑一列。每个索引查找都涉及磁盘延迟,在您的情况下,这很容易占据整个处理时间。

只需查看您发布的查询计划,在第一次查找后返回的行数已经减少到两个,并且与每一步之后可以遗漏的行数相比,查询剩余索引的成本很高(0-1行)。

因此,如果您只需要查询一个整数类型的列作为主键,您应该会遇到一些显着的性能提升,正如您在SQLite docs 中看到的那样:

SQLite 中每个表的数据都存储为 B-Tree 结构 包含每个表行的条目,使用 rowid 值作为 钥匙。这意味着按 rowid 检索或排序记录很快。 搜索具有特定 rowid 的记录,或搜索具有特定 rowid 的所有记录 指定范围内的 rowid 的速度大约是类似的两倍 通过指定任何其他 PRIMARY KEY 或索引值进行搜索。

除了一个例外,如果表的主键由 单列,并且该列的声明类型是“INTEGER” 任何大小写混合,则该列成为别名 对于rowid。这样的列通常称为“整数 主键”。

此外,您的 SQL 查询将更简单,更易于维护。

【讨论】:

    【解决方案3】:

    我认为查询优化器应该处理这种情况,但在 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)
                                                                    )
                                                     )
                                                     )
                                      )
                                      )
                       )
                       )
      )
    

    可能会变得更好(或更糟:))。

    【讨论】:

    • 您查询中的此逻辑与问题中的查询不同。
    • 是的。需要更多的妄想来解决这个问题。
    • 我觉得现在没问题了。我试图格式化以使其易于阅读/修复。 (用括号“即时”构建条件而没有愚蠢的错误)太多了。
    • 太棒了...现在似乎可以工作了,它在大约 24 毫秒内获得了 1000 行,这是完美的。
    • 其实,不...我忘记了order by statement,这仍然很慢,它将6个查询减少到2个,但它仍然制作一个临时b-tree并在结束。
    猜你喜欢
    • 2013-05-03
    • 2023-03-04
    • 1970-01-01
    • 2012-07-31
    • 1970-01-01
    • 2011-09-11
    • 2011-05-25
    • 2011-06-13
    • 1970-01-01
    相关资源
    最近更新 更多