【问题标题】:How does the IN predicate work in SQL?IN 谓词在 SQL 中如何工作?
【发布时间】:2010-10-20 03:44:33
【问题描述】:

在为this question 准备答案后,我发现我无法验证我的答案。

在我的第一份编程工作中,有人告诉我,IN () 谓词内的查询会针对父查询中包含的每一行执行,因此应避免使用 IN

例如,给定查询:

SELECT count(*) FROM Table1 WHERE Table1Id NOT IN (
SELECT Table1Id FROM Table2 WHERE id_user = 1)
表 1 行 | # 次“IN”执行 ---------------------------------- 10 | 10 100 | 100 1000 | 1000 10000 | 10000

这是正确的吗? IN 谓词实际上是如何工作的?

【问题讨论】:

  • 非常好的问题。没有看到很多问题要求详细说明 SQL 中的某些内容实际上是如何工作的。很多时候,人们更关心的是获得他们想要的结果。

标签: sql performance optimization


【解决方案1】:

不是真的。但是使用 JOIN 编写这样的查询很方便

【讨论】:

    【解决方案2】:

    取决于优化器。检查每个特定查询的确切 query plan 以查看 RDBMS 将如何实际执行该查询。

    在 Oracle 中是这样的:

    EXPLAIN PLAN FOR «your query»
    

    在 MySQL 或 PostgreSQL 中

    EXPLAIN «your query»
    

    【讨论】:

    • @vartec - 有关阅读查询计划的任何资源?这是我知识的盲区,我还没有在网上找到一篇像样的文章。
    • 它依赖于 DBMS,但基本上这可以让您了解它将执行哪些基本操作以及以何种顺序,它将使用哪个索引等。
    • 我认为您必须寻找的是有关数据库调优的文章,因为这是首先查看查询计划的目的。
    • 查看这个 SO 问题:stackoverflow.com/questions/79266/…
    • 我添加了一个新问题:stackoverflow.com/questions/761204/…
    【解决方案3】:

    这将完全取决于您使用的数据库以及确切的查询。

    查询优化器有时非常聪明 - 在您的示例查询中,我希望更好的数据库能够使用与连接相同的技术。更幼稚的数据库可能只是多次执行相同的查询。

    【讨论】:

    • 我同意,我刚刚做了一个测试查询,我相信(我不擅长阅读 ExecutionPlans)它创建了一个内部连接。
    【解决方案4】:

    现在大多数 SQL 引擎几乎总是会为 LEFT JOIN、NOT IN 和 NOT EXISTS 创建相同的执行计划

    我会说看看你的执行计划并找出答案:-)

    此外,如果 Table1Id 列的值为 NULL,您将不会得到任何数据

    【讨论】:

      【解决方案5】:

      这取决于有问题的RDBMS

      在此处查看详细分析:

      简而言之:

      1. MySQL 会将查询优化为:

        SELECT  COUNT(*)
        FROM    Table1 t1
        WHERE   NOT EXISTS
                (
                SELECT  1
                FROM    Table2 t2
                WHERE   t2.id_user = 1
                        AND t2.Table1ID = t1.Table2ID
                )
        

        并在循环中运行内部子查询,每次都使用索引查找。

        • SQL Server 将使用 MERGE ANTI JOIN

        内部子查询不会按照常识“执行”,而是同时获取查询和子查询的结果。

        详细解释见上面的链接。

        • Oracle 将使用 HASH ANTI JOIN

        内部子查询将被执行一次,并从结果集中构建一个哈希表。

        外部查询的值将在哈希表中查找。

        • PostgreSQL 将使用 NOT (HASHED SUBPLAN)

        很像Oracle

      请注意,将查询重写为:

      SELECT  (
              SELECT  COUNT(*)
              FROM    Table1
              ) - 
              (
              SELECT  COUNT(*)
              FROM    Table2 t2
              WHERE   (t2.id_user, t2.Table1ID) IN
                      (
                      SELECT  1, Table1ID
                      FROM    Table1
                      )
              )
      

      将大大提高所有四个系统的性能。

      【讨论】:

      • 我上次查看时,SQL Server 只允许从子查询返回单个列,然后在 IN 谓词中使用该列。有变化吗?
      • @RolandTumble:没有。 MySQLPostgreSQL 允许这样做,SQL ServerOracle 不允许。对于后两个系统,IN 谓词应重写为EXISTS
      【解决方案6】:

      是的,但是一旦查询处理器“找到”您要查找的值,执行就会停止...因此,例如,如果外部选择中的第一行的 Table1Id = 32,并且如果 Table2 有一条记录一个 TableId = 32,然后 一旦子查询在 Table2 中找到 TableId = 32 的行,它就会停止...

      【讨论】:

        【解决方案7】:

        您收到的关于为每一行执行子查询的警告是正确的——对于相关的子查询。

        SELECT COUNT(*) FROM Table1 a 
        WHERE a.Table1id NOT IN (
          SELECT b.Table1Id FROM Table2 b WHERE b.id_user = a.id_user
        );
        

        请注意,子查询引用了外部查询的id_user 列。 id_userTable1 的每一行上的值可能不同。因此,子查询的结果可能会有所不同,具体取决于外部查询中的当前行。 RDBMS 必须多次执行子查询,外部查询中的每一行执行一次。

        您测试的示例是一个不相关的子查询。大多数现代的 RDBMS 优化器应该能够判断子查询的结果何时取决于外部查询的每一行中的值。在这种情况下,RDBMS 会运行一次子查询,缓存其结果,并在外部查询中重复使用它作为谓词。

        PS:在 SQL 中,IN() 被称为“谓词”,而不是语句。谓词是语言的一部分,可以评估为真或假,但不一定作为语句独立执行。也就是说,您不能只将其作为 SQL 查询运行:“2 IN (1,2,3);”虽然这是一个有效的谓词,但它不是一个有效的陈述。

        【讨论】:

        • 在 MySQL 中,子查询将被优化为 NOT EXISTS(即 DEPENDENT SUBQUERY),如果存在则将使用对 INDEX ON Table2 (id_user, table1ID) 的索引访问。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多