【问题标题】:Optimize SQL Query on SQLite3 by using indexes使用索引优化 SQLite3 上的 SQL 查询
【发布时间】:2012-08-12 14:40:20
【问题描述】:

我正在尝试通过创建索引来优化 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 上发现了一些类似的问题,但没有帮助我。

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

【问题讨论】:

  • 你试过在mots(mot, fk_dictionnaires)上建立索引吗?
  • 是的,我目前在这两个字段上有一个索引。 mots(mot, fk_dictionnaires)mots(fk_dictionnaires, mot) 不同?
  • 尝试按该顺序而不是 mots(fk_dictionnaires, mot) 的索引,如果您的大部分记录都设置为相同的 fk_dictionnaires 值,这可能会产生重大影响
  • 我更改了字段顺序,但没有发现任何差异。处理时间相同,使用新索引mots(fk_dictionnaires, mot)

标签: sql performance optimization indexing


【解决方案1】:

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 没有使用它。对于“快速”查询,SQLite 似乎更喜欢使用ponderation 上的索引,以避免在查询结束时对行进行排序。如果您在 fk_dictionnaires,ponderation DESC 上添加索引,并且 SQLite 实际使用它,它可以在不进行表扫描的情况下挑选出与 fk_dictionnaires=1 匹配的行并且避免在最后进行排序。


POSTSCRIPT:我上面建议的复合索引“修复”了 OP 的性能问题,但他也询问了它的工作原理和原因。 @AGeiser,我将使用一个简短的说明来帮助您直观地理解数据库索引:

想象一下,您需要找到镇上所有姓氏以“A”开头的人。您有一个包含所有名称的目录,但它们的顺序是随机的。你做什么工作?您别无选择,只能通读整个目录,然后选择以“A”开头的目录。听起来工作量很大,对吧? (这就像一个没有索引的数据库表。)

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

现在,如果您希望所有姓名以“A”开头的人,但如果有 2 个人的姓名相同,您希望他们按邮政编码排序?即使您使用“电话簿”(即last_name,first_name 上的索引)快速获得所需的姓名,您仍然必须手动对它们进行排序......所以听起来又开始做很多工作了。什么能让这项工作变得如此轻松?

这需要另一个“电话簿”——但其中的条目首先按姓名排序,然后按邮政编码排序。使用这样的“电话簿”,您可以快速选择所需的条目范围,甚至不需要对它们进行排序——它们已经按照所需的顺序排列。 (这是last_name,first_name,postal_code上的索引。)

我认为这个插图应该清楚地说明索引如何帮助 SELECT 查询,不仅通过减少必须检查的行数,而且通过(可能)消除在需要之后单独的“排序”阶段的需要找到行。希望它也清楚地表明a,b 上的复合索引与b,a 上的复合索引完全不同。我可以继续提供更多“电话簿”示例,但这个答案会变得很长,以至于更像是一篇博客文章。为了让您了解哪些索引可能有利于查询,我推荐 O'Reilly 的《SQL 反模式》一书(尤其是第 13 章,“Index Shotgun”)。

【讨论】:

  • 设置了 LIMIT 子句。我在结果集中总是有 5 个结果。不过,如果我不使用 LIMIT,我有 411 和 fk_dictionaires=1 和 1991 没有。
  • 这表明 SQLite 对索引基数的估计方式偏离基准。它认为它只需要使用 idx_dictionnaires 索引检查 2 行。请尝试我的答案中的建议并发布结果。
  • idx_dictionnaires 是最新的,但性能是一样的。 0|0|0|SEARCH TABLE mots USING INDEX idx_dictionnaires (fk_dictionnaires=?) (~12843 rows) 0|0|0|USE TEMP B-TREE FOR ORDER BY
  • 那你可以尝试使用+ 来强制SQLite 不使用那个索引吗?
  • 在性能方面似乎比以前差了。 CPU Time: user 0.156001 sys 0.218401 但是查询计划已经改变并且使用了另一个索引。 0|0|0|SCAN TABLE mots USING INDEX idx_mots_ponderation (~32108 rows)
猜你喜欢
  • 1970-01-01
  • 2012-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-19
  • 1970-01-01
  • 1970-01-01
  • 2012-01-27
相关资源
最近更新 更多