【问题标题】:Why is the != operator faster than the = operator?为什么 != 运算符比 = 运算符快?
【发布时间】:2021-04-14 14:50:23
【问题描述】:

我有一个 SQLite 数据库并执行以下查询:

SELECT
    category,
    name,
    COUNT(side.id) as nb
FROM main
LEFT JOIN side
    ON main.id = side.id_main
WHERE category != 3
GROUP BY category, name;

此查询大约需要 0.3 秒并返回 8000 行。 以下是相关的查询计划:

id  parent  notused detail
7   0   0   SCAN TABLE main USING COVERING INDEX idx_main
19  0   0   SEARCH TABLE side USING AUTOMATIC COVERING INDEX (id_main=?)

现在,我只想选择一个类别。所以,我像这样重写了我的WHERE 子句:

SELECT
    category,
    name,
    COUNT(side.id) as nb
FROM main
LEFT JOIN side
    ON main.id = side.id_main
WHERE category = 1
GROUP BY category, name;

这个查询现在大约需要 43 秒并返回 5000 行!它长了100倍! 以下是相关的查询计划:

id  parent  notused detail
7   0   0   SEARCH TABLE main USING COVERING INDEX idx_main (category=?)
11  0   0   SCAN TABLE side

为什么等于运算符这么慢?我的查询有什么问题吗?


复制步骤:

  1. 创建以下表格
CREATE TABLE main (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category INTEGER,
name VARCHAR(64)
);

CREATE TABLE side (
id INTEGER PRIMARY KEY AUTOINCREMENT,
id_main INTEGER
);

CREATE INDEX idx_side ON side (id, id_main);

CREATE INDEX idx_main ON main (category, name);
  1. 使用以下 Perl 脚本填充表格
use strict;
use warnings FATAL => 'all';

use DBI;

my $dbh = DBI->connect("dbi:SQLite:dbname=file.db","","");

my $v = 'A';

for my $i (1..9000) {
    my $category = 1;
    $category = 2 if $i > 5000;
    $category = 3 if $i > 8000;
    $dbh->do("INSERT INTO main(category, name) VALUES($category, '$v')");
    $v++;
}

for my $i (1..100000) {
    my $r = int(rand() * 9000 + 1);
    $dbh->do("INSERT INTO side(id_main) VALUES($r)");
}

【问题讨论】:

  • 嗨皮埃尔,请更新帖子,说明“!=”和“=”查询的计划。在这里你可以找到如何获得那些sqlite.org/eqp.html
  • 是的,请在两个查询上运行EXPLAIN。你所看到的对我来说似乎非常违反直觉(但对一个好问题 +1)。
  • @ekochergin 完成。

标签: sql sqlite


【解决方案1】:

第一步的区别是可以理解的:= 允许 SQLite 查找给定值的行 (SEARCH) 而!= 要求它遍历所有值 (SCAN)

问题是:为什么它会在第一种情况下创建一个临时 (AUTOMATIC) 索引(恰好非常有效),而不是在第二种情况下?

我认为这是因为在没有额外信息的情况下,查询规划器假定 = 子句产生的匹配项将少于 !=,并且构建临时索引的成本是不合理的。

解决办法是让SQLite用ANALYZE收集信息。查询计划变为:

QUERY PLAN
|--SEARCH TABLE main USING COVERING INDEX idx_main (category=?)
`--SEARCH TABLE side USING AUTOMATIC COVERING INDEX (id_main=?)

另一种解决方案是创建所需的索引,这样 SQLite 就不必构建它了。

CREATE INDEX idx_side2 ON side (id_main);

使用以下查询计划(即使没有分析):

QUERY PLAN
|--SEARCH TABLE main USING COVERING INDEX idx_main (category=?)
`--SEARCH TABLE side USING COVERING INDEX idx_side2 (id_main=?)

【讨论】:

    【解决方案2】:

    感谢您提出有趣的问题并解释计划。

    计划向我们展示了为什么“!=”的运行时间比“=”查询的运行时间短。

    “!=”查询扫描 idx_main 索引以获取数据,然后使用另一个自动创建的索引在“side”表中查找相应的行。所以,我认为它就像数据库完全扫描了一个索引,然后在另一个索引中搜索合适的数据。

    “=”查询首先使用索引搜索 category=1 的行,然后全面扫描整个“side”表,这需要很长时间,因为它需要检查该表中的每个元组。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-05
      • 1970-01-01
      相关资源
      最近更新 更多