【问题标题】:Escape wildcards (%, _) in SQLite LIKE without sacrificing index use?在不牺牲索引使用的情况下,在 SQLite LIKE 中转义通配符(%,_)?
【发布时间】:2012-10-14 21:43:12
【问题描述】:

我有几个关于 SQLite 查询的问题。实际上我开始认为 SQLite 不是为超过 10 行的表设计的,真的,SQLite 是一场噩梦。

以下查询

SELECT * FROM [Table] WHERE [Name] LIKE 'Text%'

它工作正常。 EXPLAIN 表示使用了索引,大约在70ms之后返回结果。

现在我需要从 .NET SQLite 驱动程序运行这个查询,所以我正在更改查询

SELECT * FROM [Table] WHERE [Name] LIKE @Pattern || '%'

未使用索引。当我在任何 SQLite 工具中运行以下查询时,也不会使用索引

SELECT * FROM [Table] WHERE [Name] LIKE 'Text' || '%'

所以我猜 SQLite 没有实现任何类型的预处理逻辑。

好的。我们试着解决一下,我还在绑定变量,在做下面的事情

SELECT * FROM [Table] WHERE [Name] LIKE @Pattern

但现在我将% 通配符附加到我的模式字符串的末尾,就像这样

command.Parameters.Add(new SQLiteParameter("@Pattern", pattern + '%'));

它的工作速度很慢。我不能说为什么,因为当我从 SQLite 工具运行这个查询时它运行良好,但是当我从 .NET 代码绑定这个变量时它运行缓慢。

好的。我仍在努力解决这个问题。我正在摆脱模式参数绑定并动态构建此条件。

pattern = pattern.Replace("'", "''");
pattern = pattern.Replace("%", "\\%");
where = string.Format("LIKE '{0}%' ESCAPE '\\'", pattern);

索引不再使用。由于ESCAPE,它没有被使用。我在运行时看到了这一点

EXPLAIN QUERY PLAN SELECT * FROM [Table] WHERE [Name] LIKE 'Text%' ESCAPE '\'

一旦我删除ESCAPE,它就会再次开始使用索引,查询会在 60-70 毫秒内完成。

更新

这是结果。

EXPLAIN QUERY PLAN
SELECT * FROM [RegistryValues]
WHERE
     [ValueName] LIKE 'windir%' ESCAPE '\' 

SCAN TABLE RegistryValues (~3441573 rows)

和没有ESCAPE的那个

EXPLAIN QUERY PLAN
SELECT * FROM [RegistryValues]
WHERE
     [ValueName] LIKE 'windir%'

SEARCH TABLE RegistryValues USING INDEX IdxRegistryValuesValueNameKeyIdKeyHiveFileId (ValueName>? AND ValueName<?) (~31250 rows)

更新

刚刚找到这个

http://www.sqlite.org/optoverview.html

4.0 LIKE 优化

The ESCAPE clause cannot appear on the LIKE operator

那我该怎么办呢?

我理解对了吗?我无法在SQLite 中使用LIKE 运算符搜索包含通配符的字符串。我说通配符是指_%^!

这是不可能的,因为我无法逃脱它们。 其实可以,但是这种情况下不能使用索引,所以查询效率不高。

我说的对吗?

【问题讨论】:

  • 我不熟悉 SQLite 细节,但大多数 DBMS 通常会独立于绑定参数 value 准备查询 - 事实上,查询准备甚至可以在参数之前进行值是已知的。因此,LIKE @Pattern 无法针对索引进行优化,因为@Pattern 可能是“%suffix”。正如您已经指出的那样,解决方案是动态构建查询 - 坦率地说,我不明白为什么 ESCAPE 会搞砸。
  • 是的,我也没有看到它,但这就是正在发生的事情。我可以理解为什么它不使用索引进行连接,但是escape 有什么问题?不幸的是,我需要转义 % 符号以允许搜索以 %windir% 之类的开头的字符串。
  • 顺便说一句,它也不使用以下索引。 SELECT * FROM [Table] WHERE [Name] LIKE 'Text' || '%'
  • @axe ESCAPE 的行为似乎是 SQLite 的限制。引用The SQLite Query Planner: "由 LIKE 或 GLOB 运算符组成的术语有时可用于约束索引。这种使用有很多条件:... 3. ESCAPE 子句不能出现在 LIKE 运算符上。”
  • @Branko Dimitrijevic:是的,这已经在我的问题中引用了。那么解决方案是什么?查询工作 40 秒显然是不可接受的。另一方面,不允许用户搜索包含下划线的字符串也是不可接受的。

标签: c# .net performance sqlite indexing


【解决方案1】:

% 位于末尾时,索引只能与LIKE 子句一起使用,以便SQLite 可以将其重写为两个简单的比较(如EXPLAIN 输出所示)。

因此,要获得相同的效果,请自己编写比较。 这要求您构造一些保证比较“大于”任何匹配值的字符串(注意非 ASCII 字符)。 而不是:

... WHERE Name LIKE 'Text%'

使用:

... WHERE Name BETWEEN 'Text' AND 'Textzzzzzzzzzzzzz'

或者,作为参数:

... WHERE Name BETWEEN @Pattern AND @Pattern || 'zzzzzzzzzz'

(这个结构永远不需要转义。:)

【讨论】:

  • 真是天才! :) 正是我需要的。谢谢。
【解决方案2】:

确保将查询包装在事务中。我一直在通过没有它的库进行一些查询,添加它后速度提高了 10 倍。

可能是关于性能问题的最佳资源:Improve INSERT-per-second performance of SQLite?

【讨论】:

  • SQLite Expert(我用于测试查询的工具)默认将每个查询包装在事务中。问题实际上在于索引的使用,我认为事务在这里没有帮助(请参阅原始帖子中的更新)。
  • 显式事务在此无效 - 问题在于 single DQL 语句(将根据需要在其自己的事务中隐式运行)。
  • @pst:哦,对不起。我没有注意到评论是由另一个人写的。我认为这是对答案的一种解释。现在很清楚了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-11
  • 1970-01-01
  • 2012-10-17
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
相关资源
最近更新 更多