使用索引优化SQLite3上的SQL查询

时间:2012-08-16 09:08:06

标签: sql performance optimization indexing

我正在尝试通过创建索引来优化SQL查询以获得最佳性能。

表格定义

CREATE TABLE Mots (
  numero            INTEGER NOT NULL, 
  fk_dictionnaires integer(5) NOT NULL, 
  mot              varchar(50) NOT NULL, 
  ponderation      integer(20) NOT NULL,
  drapeau varchar(1) NOT NULL,
  CONSTRAINT pk_mots PRIMARY KEY(numero),
  CONSTRAINT uk_dico_mot_mots UNIQUE(fk_dictionnaires, mot),
  CONSTRAINT fk_mots_dictionnaires FOREIGN KEY(fk_dictionnaires) REFERENCES Dictionnaires(numero)
  );

索引定义

CREATE INDEX idx_dictionnaires ON mots(fk_dictionnaires DESC);
CREATE INDEX idx_mots_ponderation ON mots(ponderation);
CREATE UNIQUE INDEX idx_mots_unique ON mots(fk_dictionnaires, mot);

SQL查询:

SELECT numero, mot, ponderation, drapeau 
FROM mots 
WHERE mot LIKE 'ar%' 
   AND fk_dictionnaires=1 
   AND LENGTH(mot)>=4 
   ORDER BY ponderation DESC 
LIMIT 5;

查询计划

0|0|0|SEARCH TABLE mots USING INDEX idx_dictionnaires (fk_dictionnaires=?) (~2 rows)
0|0|0|USE TEMP B-TREE FOR ORDER BY

似乎没有使用定义的索引,查询持续(根据.timer):

CPU Time: user 0.078001 sys 0.015600

然而,当我删除fk_dictionnaires = 1时。我的索引使用正确,性能大约为0.000000-0.01XXXXXX秒

0|0|0|SCAN TABLE mots USING INDEX idx_mots_ponderation (~250000 rows)

我在stackoverflow上发现了一些类似的问题,但没有anwser帮助我。

如何通过使用索引或/和更改SQL查询来提高性能? 提前谢谢。

1 个答案:

答案 0 :(得分:5)

SQLite似乎认为idx_dictionnaires索引非常稀疏,并得出结论,如果它使用idx_dictionnaires扫描,它只需要检查几行。但是,您引用的性能结果表明它必须检查的不仅仅是几行。首先,为什么不尝试ANALYZE mots,所以SQLite会有关于每个索引的基数的最新信息?

以下是其他可能有帮助的内容,来自SQLite文档:


通过将一元+运算符添加到列名称,可以手动取消WHERE子句的条款以与索引一起使用。一元+是无操作,不会减慢对该术语指定的测试的评估。但它会阻止该术语限制索引。因此,在上面的示例中,如果查询被重写为:

SELECT z FROM ex2 WHERE +x=5 AND y=6;

x列上的+运算符将阻止该术语约束索引。这将强制使用ex2i2索引。

请注意,一元+运算符还会从表达式中删除类型相关性,在某些情况下,这会导致表达式含义的细微变化。在上面的例子中,如果列x具有TEXT亲和力,那么比较" x = 5"将以文字形式完成。但+运算符会删除亲和力。所以比较" + x = 5"将x列中的文本与数值5进行比较,并且始终为false。


如果ANALYZE mots不足以帮助SQLite选择要使用的最佳索引,则可以使用此功能强制它使用您想要的索引。

您也可以尝试复合索引 - 看起来您已经在fk_dictionnaires,mot上定义了一个,但SQLite并没有使用它。对于&#34;快速&#34;查询,SQLite似乎更喜欢使用ponderation上的索引,以避免在查询结束时对行进行排序。如果您在fk_dictionnaires,ponderation DESC上添加索引,并且SQLite实际使用它,则可以选择与fk_dictionnaires=1匹配的行而不使用表扫描以避免在末尾进行排序。< / p>


POSTSCRIPT :我在上面建议的复合索引&#34;固定&#34; OP的性能问题,但他也询问了它的工作原理和原因。 @AGeiser,我将使用简短的插图来帮助您直观地理解数据库索引:

想象一下,你需要找到你所在城镇的所有人,他们的姓氏以&#34; A&#34;开头。您有一个包含所有名称的目录,但它们是随机排列的。你是做什么?你别无选择,只能阅读整个目录,并挑选以&#34; A&#34;开头的目录。听起来很多工作,对吧? (这就像没有索引的DB表。)

但是,如果有人给你一本电话簿,所有名字都按字母顺序排列怎么办?现在,您可以找到以&#34; A&#34;开头的第一个和最后一个条目。 (使用二进制搜索之类的东西),并获取该范围内的所有条目。你甚至不必查看书中的所有其他名字。这将方式更快。 (这就像带有索引的DB表;在这种情况下,将其称为last_name,first_name上的索引。)

现在如果你想要名字以&#34; A&#34;开头的所有人怎么办?但是如果2个人的名字相同,你希望他们按邮政编码排序吗?即使您使用&#34;电话簿快速获得所需的名称&#34; (即last_name,first_name上的索引),你仍然需要手动对它们进行排序......所以它开始听起来像是很多工作。什么能让这项工作变得非常简单?

需要另一本电话簿&#34; - 但是首先按名称排序条目,然后按邮政编码排序。使用电话簿&#34;这样,你可以快速选择你需要的条目范围,你甚至不需要对它们进行排序 - 它们已经按照所需的顺序排列。 (这是last_name,first_name,postal_code的索引。)

我认为这个例子应该清楚索引如何帮助SELECT查询,不仅仅是通过减少必须检查的行数,而且还可以(可能)消除对单独&#34;排序的需求#34 ;找到所需行后的阶段。希望它也清楚地表明a,b上的复合索引与b,a上的复合索引完全不同。我可以继续提供更多的电话簿&#34;例子,但这个答案会变得太长,以至于它更像博客文章。为了建立你对哪些索引可能有益于查询的直觉,我推荐来自O&#39; Reilly的书#14; SQL Antipatterns&#34; (特别是第13章,#34; Index Shotgun&#34;)。