【问题标题】:Is SQL IN bad for performance?SQL IN 对性能不利吗?
【发布时间】:2010-11-04 01:39:33
【问题描述】:

我有一个类似的查询:

SELECT FieldX, FieldY FROM A
WHERE FieldW IN (108, 109, 113, 138, 146, 160,
307, 314, 370, 371, 441, 454 ,457, 458, 479, 480,
485, 488, 490, 492, 519, 523, 525, 534, 539, 543,
546, 547, 550, 564, 573, 629, 642, 643, 649, 650,
651, 694, 698, 699, 761, 762, 768, 772, 773, 774,
775, 778, 784, 843, 844, 848, 851, 852, 853, 854,
855, 856, 857, 858, 859, 860, 861, 862, 863, 864,
865, 868, 869, 871, 872, 873, 891) 

拥有这么多选项的 IN 子句,对查询性能不利吗?我在我的应用程序中遇到了很多超时,我相信这可能是此类问题的根源。我可以使用任何好的 SQL 提示优化查询而不删除数字吗?

编辑:

@KM 这些是不同表中的键。这是一个论坛应用程序,简单解释一下:c#从数据库中获取所有论坛,并将其存储在应用程序缓存中。在 C# 调用为这些论坛和该用户获取线程的过程之前,c# 会执行一些逻辑过滤“所有论坛”集合,考虑权限和一些业务逻辑。超时发生在数据库上,而不是应用程序本身。对查询执行所有这些逻辑将需要大量内部连接,而且我不能 100% 确定我可以在过程中完成所有这些操作。

我正在使用 SQL Server 2000

【问题讨论】:

  • 这些是随机数吗?还是他们来自某个地方?也许您可以制作一个单独的表格并将这些数字放入其中并为其编制索引,然后在您的选择语句中使用连接。
  • 不,它们不是随机数。之前执行了一些查询(然后缓存)以返回这些 id。进行联接相当困难,因为在执行该(缓存)查询之后,今天在 C# 中完成了一些应用程序逻辑。
  • 您没有提供太多信息,但如果这些 (IN n, n,n ..) 值是不同表中的键,并且有一些共同点 (status=xyz),则可能可以将 INNER JOIN 加入到该表中,这可能会更快。如果您根据某些条件选择所有这些 ID(在不同的查询中),然后从该结果集中构建此 select 语句,那么您应该尝试使用 INNER JOIN 方法
  • 然而,从 SQL Server 的角度来看,它们是随机的,它必须单独查询每一个。没有模式我不知道你可以写一个好的提示。您可以尝试多线程 - 将数字分解成更小的块,并使用更小的列表发送更多查询。
  • 我不确定您所说的“缓存”是什么意思。我怀疑某些异常情况可能导致发生锁定,并且查询在锁定时超时。

标签: sql sql-server-2000


【解决方案1】:

对于这样的查询,我通常会使用用户定义的表类型。

CREATE TYPE [dbo].[udt_int] AS TABLE (
    [id] [int] NOT NULL
)

使用表格变量并为每个数字填充行,您可以:

SELECT 
    FieldX, 
    FieldY
FROM A
INNER JOIN @myIds B ON
    A.FieldW = B.id

【讨论】:

  • 这会给你带来更差的性能。 @myIds 表中有 100 个身份,使用 IN(从 @myIds 中选择 id)执行需要 5 秒的繁重查询使用 SqlServer 13 中的 INNER JOIN 降级到 12 秒
【解决方案2】:

您可以尝试创建一个临时表,将值插入其中,然后在 IN 谓词中使用该表。

AFAIK,SQL Server 2000 无法构建一组常量的哈希表,这剥夺了优化器使用 HASH SEMI JOIN 的可能性。

仅当您在 FieldW 上没有索引(您应该有)时,这才会有所帮助。

您也可以尝试将您的 FieldXFieldY 列包含到索引中:

CREATE INDEX ix_a_wxy ON a (FieldW, FieldX, FieldY)

这样查询只能通过使用索引来提供。

SQL Server 2000 缺少CREATE INDEXINCLUDE 选项,这可能会稍微降低DML 的性能,但会提高查询性能。

更新:

根据您的执行计划,我认为您需要在 (SettingsID, SectionID) 上的复合索引

SQL Server 2000 确实可以从一个常量列表中构建一个哈希表(并且做到了),但是对于查询查询,Hash Semi Join 的效率很可能低于Nested Loop

附带说明:如果您需要知道满足WHERE 条件的行数,请不要使用COUNT(column),而是使用COUNT(*)

COUNT(column) 不计算column 值为NULL 的行。

这意味着,首先,您可以获得意想不到的结果,其次,如果您的列未被服务的索引覆盖,优化器将需要执行额外的Key Lookup / Bookmark Lookup WHERE 条件。

因为ThreadId 似乎是CLUSTERED PRIMARY KEY,所以这个查询没问题,但一般尽量避免。

【讨论】:

  • 我很想看到有人测试这个断言,即测试 IN 的性能与创建临时表并加入...
  • @tekBlues:手边没有 2000,抱歉。 2005 使用 CONSTANT SCAN 方法在 IN 子句值上构建一个哈希表。您能否为您的查询构建执行计划并在此处发布?
  • @tekBlues:哦,对不起,没注意到你不是@op。没关系:)
  • 大量工作只是为了确定哪些查询超时。为什么不只看痕迹?
  • @tekBlues - 我们已经为此目的使用了创建临时表的方法。为了提高性能,您必须在临时表中插入行,然后更新临时表上的统计信息 - 否则 SQL 执行计划优化器会认为临时表为空并执行完整扫描。这种方法确实比使用 IN 或 OR 子句的查询更快,因为它可以避免解析查询和构建执行计划的步骤。
【解决方案3】:

你可以试试这样的:

select a.FieldX, a.FieldY
from (
    select FieldW = 108 union
    select FieldW = 109 union
    select FieldW = 113 union
    ...
    select FieldW = 891
) _a
join A a on a.FieldW = _a.FieldW

它可能适合您的情况,例如当您想要动态生成单个 SQL 语句时。在我的机器 (SQL Server 2008 Express) 上,使用少量 (5) 的 FieldW 值和大量 (100,000) 行进行测试,这使用了 A 上的索引查找,并在 A 和 _a 之间使用嵌套循环连接,这可能就是你要找的。​​p>

【讨论】:

    【解决方案4】:

    这是你的答案...

    http://www.4guysfromrolla.com/webtech/031004-1.shtml

    基本上,您想创建一个函数来拆分字符串并使用拆分内容填充临时表。然后您可以加入该临时表并操作您的数据。以上解释的很好。我经常使用这种技术。

    在您的特定情况下,使用连接到临时表而不是 in 子句,要快得多。

    【讨论】:

      【解决方案5】:

      如果您在 FieldW 上有一个好的索引,那么使用该 IN 是完全正确的。

      我刚刚测试过,SQL 2000 在使用 IN 时会进行聚集索引扫描。

      【讨论】:

      • 那未必是件好事。它应该进行查找而不是扫描,这表明使用 IN 并不是“完全正确”。但表的大小、基数和其他因素也很重要。
      • @tekBlues:你能看看它是否在恒定扫描上进行哈希匹配?只需在查询末尾添加一个 OPTION (HASH JOIN) 即可查看计划
      • @Quassnoi,我添加了 HASH JOIN 并且执行计划没有改变。
      • 理想情况下,您希望查询专门针对每个值搜索索引,而不是从头到尾读取整个索引(“扫描”)。如果只有一个键,或者键是有序集,则更有可能这样做。但是,如果键不在表中(已排序或易于排序),则查询优化器可能会做一些次优的事情。此外,很明显,您已经在(单一可能的)聚集索引中获得了这些值,这对于 OP 可能是正确的,也可能不是正确的,并且可能甚至可能不重要。
      • 实际问题是应用程序超时,可能是在这个查询上,可能是因为速度慢,也可能是因为锁定。距离构建哈希表还有很长的路要走。您至少不想先看一个查询计划吗?只有当我们知道这是一个问题时,查询才值得改进。
      【解决方案6】:

      使用 IN 运算符编写查询时有几个注意事项可能会影响性能。

      首先,大多数数据库通常在内部重写 IN 子句以使用 OR 逻辑连接词。因此col IN ('a','b','c') 被重写为:(COL = 'a') OR (COL = 'b') or (COL = 'c')。假设您在col 上有一个索引,则这两个查询的执行计划可能是相同的。

      其次,当使用带有可变数量参数的 IN 或 OR 时,您会导致数据库在每次参数更改时都必须重新解析查询并重建执行计划。 构建查询的执行计划可能是一个昂贵的步骤。大多数数据库使用 EXACT 查询文本作为键来缓存它们运行的​​查询的执行计划。如果您执行类似的查询,但在谓词中使用不同的参数值 - 您很可能会导致数据库花费大量时间来解析和构建执行计划。这就是为什么bind variables are strongly recommended 是确保最佳查询性能的一种方式。

      第三,许多数据库对它们可以执行的查询的复杂性有限制 - 其中一个限制是谓词中可以包含的逻辑连接数。十几个值不太可能达到数据库的内置限制,但是如果您希望将数百或数千个值传递给 IN 子句 - 它肯定会发生。在这种情况下,数据库将简单地取消查询请求。

      第四,在谓词中包含 IN 和 OR 的查询不能总是在并行环境中以最佳方式重写。 并行服务器优化没有得到应用的各种情况 - MSDN has a decent introduction 优化查询为并行。不过,一般来说,使用 UNION ALL 运算符的查询在大多数数据库中都是可并行化的 - 并且在可能的情况下优先于逻辑连接词(如 OR 和 IN)。

      【讨论】:

      • 感谢您的评论,这是每个人都需要知道的。我遇到了一个 In 语句,它被传递了 25k 个项目去爱传统代码。 :D
      • "假设你在 col 上有一个索引,两个查询的执行计划可能是相同的。"因此,如果该列未编入索引,那么语法之间是否存在性能差异?哪一个在未索引的列上表现更好?这是为什么呢?
      • IN参数的顺序会不会对性能有影响。例如,我应该在 IN 关闭之前对数字进行排序吗?
      • 如果列有某种范围 - 那么这里有一些很好的解释,dba.stackexchange.com/questions/207255/…
      • 如果我们使用内连接而不是 IN 运算符会怎样。它会提高性能吗?
      【解决方案7】:

      有更好的编码方法,但我怀疑它是超时的原因,特别是如果它只是一个 SELECT。不过,您应该能够通过查看查询跟踪来确定这一点。但重新编码这将是通过猜测进行优化,而且不太可能是猜测。

      让我们从实际超时的查询的查询计划开始。你确定是哪个查询吗?

      【讨论】:

        【解决方案8】:

        根据您的数据分布,WHERE 子句中的附加谓词可能会提高性能。例如,如果 id 的集合相对于表中的总数较小,并且您知道这些 id 相对靠近(也许它们通常是最近添加的,因此聚集在范围的高端),您可以尝试包含谓词“AND FieldW BETWEEN 109 AND 891”(在 C# 代码中确定集合中的最小和最大 ID 之后)。对这些列(如果已编制索引)进行范围扫描可能比当前使用的更快。

        【讨论】:

          【解决方案9】:

          IN 与编写大量 OR 完全相同。并且 OR 经常使查询不可搜索,因此您的索引可能会被忽略并且计划进行全面扫描。

          【讨论】:

            【解决方案10】:

            如果您可以使用除 IN 以外的其他东西:就这样做(在某些情况下,我使用 IN 并不是一个好方法:我可以轻松地用存在替换它并且它更快)

            在你的情况下:看起来还不错。

            【讨论】:

              【解决方案11】:

              只能根据您正在尝试做的事情来判断绩效。在这种情况下,您请求检索大约 70 行(假设它们是唯一值),因此您可以预期检索单个值的持续时间约为 70 倍。由于缓存或课程,它可能较少。

              但是,查询优化器可能需要或选择执行全表扫描以检索值,在这种情况下,性能与通过相同的访问计划检索单个值几乎没有区别。

              【讨论】:

                【解决方案12】:

                表的大小将决定使用此语句时的速度。如果它不是一个非常大的表...此语句不会影响您的性能。

                【讨论】:

                  【解决方案13】:

                  基本上,where 子句的作用是“FieldW = 108 OR FieldW = 109 OR FieldW = 113...”。有时,您可以通过执行多项选择并将它们与 union 结合来获得更好的性能。例如:

                  SELECT FieldX, FieldY FROM A WHERE FieldW = 108
                  UNION ALL
                  SELECT FieldX, FieldY FROM A WHERE FieldW = 109
                  

                  但是,当您与这么多值进行比较时,这当然是不切实际的。

                  另一种选择可能是将这些值插入到临时表中,然后将 A 表连接到该临时表。

                  【讨论】:

                  • 我会警惕使用 UNION 语句,尤其是在这种情况下。实际上,一个 UNION 它对最终结果集执行相当于 SELECT DISTINCT 的操作。换句话说,UNION 获取两个相似记录集的结果,将它们组合起来,然后执行 SELECT DISTINCT 以消除任何重复的行。换句话说,你会在后台运行指数级的 SELECTS。
                  【解决方案14】:

                  通常,IN 子句对性能有害,但什么是“坏”取决于应用程序、数据、数据库大小等。您需要测试自己的应用程序,看看什么是最好的。

                  【讨论】:

                  • 嗨 Bryan,“对性能有害”是什么意思 场景是我想过滤某个字段的某些值。最好的方法是什么?恕我直言,正在使用 IN 子句
                  猜你喜欢
                  • 2018-03-25
                  • 2020-10-26
                  • 2022-10-19
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-09-25
                  • 2012-02-28
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多