优化:WHERE x IN (1, 2 .., 100.000) vs INNER JOIN tmp_table USING(x)?
Posted
技术标签:
【中文标题】优化:WHERE x IN (1, 2 .., 100.000) vs INNER JOIN tmp_table USING(x)?【英文标题】:Optimization: WHERE x IN (1, 2 .., 100.000) vs INNER JOIN tmp_table USING(x)? 【发布时间】:2016-03-06 22:35:36 【问题描述】:我最近参观了一次有趣的求职面试。那里有人问我一个关于使用包含长标量列表(即数千个值)的WHERE..IN
子句优化查询的问题。这个问题不是关于IN
子句中的子查询,而是关于简单的标量列表。
我马上回答说,这可以使用 INNER JOIN
和另一个表(可能是临时表)进行优化,该表仅包含那些标量。我的回答被接受了,并且审阅者有一条注释,“目前没有数据库引擎可以优化长 WHERE..IN
条件以具有足够的性能”。我点了头。
但是当我走出去的时候,我开始有些怀疑了。这种情况似乎相当微不足道,并被广泛用于现代 RDBMS 无法对其进行优化。所以,我开始了一些挖掘。
PostgreSQL:
似乎 PostgreSQL parse scalar IN()
constructions into ScalarArrayOpExpr
structure,也就是 sorted。此结构稍后在索引扫描期间用于定位匹配的行。对于此类查询,EXPLAIN ANALYZE
仅显示一个循环。没有完成任何连接。所以,我希望这样的查询比 INNER JOIN 更快。我在现有数据库上尝试了一些查询,我的测试证明了这一点。但我并不关心测试的纯度,而且 Postgres 在 Vagrant 之下,所以我可能错了。
MSSQL 服务器:
MSSQL 服务器builds a hash structure from the list of constant expressions and then does a hash join with the source table。尽管似乎没有进行排序,但我认为这是性能匹配。我没有做任何测试,因为我对这个 RDBMS 没有任何经验。
MySQL 服务器:
The 13th of these slides 说,在 5.0 之前,这个问题确实发生在 mysql 的某些情况下。但除此之外,我没有发现任何其他与不良IN ()
治疗有关的问题。不幸的是,我没有找到任何逆向证明。如果你这样做了,请踢我。
SQLite:
Documentation page 暗示了一些问题,但我倾向于相信那里描述的东西确实是概念层面的。没有找到其他信息。
所以,我开始认为我误解了我的面试官或滥用了 Google ;) 或者,可能是因为我们没有设置任何条件,我们的谈话变得有点模糊(我们没有指定任何具体的 RDBMS或其他条件。那只是抽象的谈话)。
看起来,数据库将IN()
重写为一组OR
语句(这有时会导致列表中的NULL
值出现问题,顺便说一句)的日子已经很久了。还是不行?
当然,如果标量列表比允许的数据库协议包长,INNER JOIN
可能是唯一可用的解决方案。
我认为在某些情况下,单独的查询解析时间(如果没有准备好)会影响性能。
此外,数据库可能无法准备 IN(?)
查询,这将导致一次又一次地重新解析它(这可能会降低性能)。实际上,我从未尝试过,但我认为即使在这种情况下,查询解析和规划与查询执行相比也并不大。
但除此之外,我没有看到其他问题。好吧,除了只是遇到这个问题的问题。如果您有查询,其中包含数千个 ID,则说明您的架构有问题。
你呢?
【问题讨论】:
根据我的经验,SQL Server 在大量 IN 参数上出现查询计划程序超时。 虽然有趣但不适合这个网站。你知道...我投票结束。 我写了This thing,这更多地与随机数有关。我确实做到了端到端。我所说的附录 C 在列表中使用了大。一千个元素。结果十分之二秒。 我在IN()
中看到了一个1TB MySQL表有70K值的案例。有效。考虑到它必须做多少工作,它“相当”快。它还发现了 MySQL (4.1(?)) 中的一些内存问题。
@RickJames 该查询的解释是什么?时间是什么?
【参考方案1】:
我认为这是糟糕的应用程序设计。那些使用 IN
运算符的值很可能不是硬编码的,而是动态的。在这种情况下,我们应该始终使用准备好的语句,这是防止 SQL 注入的唯一可靠机制。
在每种情况下,它都会导致动态格式化准备好的语句(因为占位符的数量也是动态的)并且还会导致过度的硬解析(与我们拥有的 IN
值的数量一样多的唯一查询 - IN (?)
, IN (?,?)
, ...)。
我要么将这些值加载到表中,如您提到的那样使用连接(除非加载开销太大),要么使用 Oracle 流水线函数IN foo(params)
,其中 params 参数可以是来自内存(PLSQL/Java 等)的复杂结构(数组)。
如果值的数量更大,我会考虑使用EXISTS (select from mytable m where m.key=x.key)
或EXISTS (select x from foo(params)
而不是IN
。在这种情况下,EXISTS
提供比IN
更好的性能。
【讨论】:
我认为这是糟糕的应用程序设计您的整个答案与问题相切。 我的答案可能最好作为对原始问题的评论,因为它实际上不是答案。我完全同意 Vladislav 的句子“如果您有查询,其中包含数千个 ID,那么您的架构有问题。”这意味着没有必要回答这个问题,因为关于优化 SQL 语言的错误使用的学术讨论变得毫无用处。 我不确定使用带有大 ID 列表的 IN 总是一个糟糕的架构。我认为这取决于任务,在某些情况下可能是必要的。尽管在大多数情况下,应仔细修改架构以检查是否可以避免此类情况。【参考方案2】:只有在列表上建立索引(最好是主键索引)时,你的答案才是正确的,除非列表真的很小。
任何关于优化的描述肯定是特定于数据库的。但是,MySQL 非常具体地说明了它如何优化in
:
如果 expr 等于 IN 列表中的任何值,则返回 1,否则 返回 0。如果所有值都是常量,则根据 到 expr 的类型并排序。然后完成对项目的搜索 使用二分查找。这意味着如果 IN 值,IN 非常快 list 完全由常量组成。
在这种情况下,使用IN
肯定会比使用另一个表更快——并且可能比使用主键索引的另一个表更快。
我认为 SQL Server 将 IN
替换为 OR
s 的列表。然后这些将被实现为顺序比较。请注意,如果某些元素比其他元素更常见并且这些元素出现在列表的首位,则顺序比较可能比二分查找更快。
【讨论】:
以上是关于优化:WHERE x IN (1, 2 .., 100.000) vs INNER JOIN tmp_table USING(x)?的主要内容,如果未能解决你的问题,请参考以下文章
sql面试题_SQl优化技巧_1注意通配符中like的使用,百分号放后面_2避免在where子句中对字段进行函数操作_3在子查询当中,尽量用exists代替in_4where子句中尽量不要使用(代码片