【问题标题】:Best way to test if a row exists in a MySQL table测试 MySQL 表中是否存在行的最佳方法
【发布时间】:2010-12-13 04:33:38
【问题描述】:

我正在尝试找出表中是否存在一行。使用 MySQL,做这样的查询是否更好:

SELECT COUNT(*) AS total FROM table1 WHERE ...

并检查总数是否为非零,或者执行如下查询是否更好:

SELECT * FROM table1 WHERE ... LIMIT 1

并检查是否返回了任何行?

在这两个查询中,WHERE 子句都使用索引。

【问题讨论】:

    标签: sql mysql performance exists


    【解决方案1】:

    COUNT 查询更快,虽然可能不明显,但就获得所需结果而言,两者都应该足够了。

    【讨论】:

    • 这是特定于数据库的。已知 COUNT(*) 在 PostgreSQL 中很慢。最好选择 PK 列并查看它是否返回任何行。
    • COUNT(*) 在 InnoDB 中虽然很慢
    【解决方案2】:

    你也可以试试EXISTS:

    SELECT EXISTS(SELECT * FROM table1 WHERE ...)
    

    根据the documentation,你可以SELECT 任何东西。

    传统上,EXISTS 子查询以 SELECT * 开头,但它可以 以 SELECT 5 或 SELECT column1 或任何内容开头。 MySQL 忽略此类子查询中的 SELECT 列表,因此没有区别。

    【讨论】:

    • ...EXISTS( SELECT 1/0 FROM someothertable) 测试。对于 SQL Server 和 Oracle - 使用 *、1 或 NULL 没有区别,因为 EXISTS 仅测试基于 WHERE 条件匹配的 1+ 的布尔值。
    • 伙计们,它在链接到此答案的文档第 2 段中说,“传统上,EXISTS 子查询以 SELECT * 开头,但它可以以 SELECT 5 或 SELECT column1 或任何内容开头. MySQL 在这样的子查询中忽略了 SELECT 列表,所以没有区别。”
    • @ChrisThompson :执行语句时会发生什么?我的意思是结果集包含什么?
    • @Ashwin,它包含 0(不存在)或 1(存在)。
    • 我觉得你的查询是多余的,我测试过,这个查询SELECT 1 FROM table1 WHERE col = $var LIMIT 1比你的查询要快。那么你的查询有什么优势呢?
    【解决方案3】:

    COUNT(*) 在 MySQL 中进行了优化,所以一般来说前者的查询速度可能会更快。

    【讨论】:

    • 您指的是 MyISAM 对选择整个表的计数的优化吗?如果存在 WHERE 条件,我认为这没有帮助。
    【解决方案4】:

    对于非 InnoDB 表,您还可以使用信息模式表:

    http://dev.mysql.com/doc/refman/5.1/en/tables-table.html

    【讨论】:

      【解决方案5】:

      我会选择COUNT(1)。它比COUNT(*) 快,因为COUNT(*) 测试该行中是否至少有一列是!= NULL。你不需要那个,特别是因为你已经有了一个条件(WHERE 子句)。 COUNT(1) 而是测试 1 的有效性,它始终有效并且测试时间要少得多。

      【讨论】:

      • -1 这是错误的。 COUNT(*) 不查看列值 - 它只计算行数。在这里查看我的答案:stackoverflow.com/questions/2876909/…
      • COUNT() 比 EXISTS 慢得多,因为 EXISTS 在第一次找到行时会返回
      【解决方案6】:

      我最近对这个主题做了一些研究。如果字段是 TEXT 字段,非唯一字段,实现方式必须不同。

      我用 TEXT 字段做了一些测试。考虑到我们有一个包含 1M 条目的表这一事实。 37 个条目等于“某物”:

      • SELECT * FROM test WHERE text LIKE '%something%' LIMIT 1mysql_num_rows():0.039061069488525s。 (更快)
      • SELECT count(*) as count FROM test WHERE text LIKE '%something%: 16.028197050095s。
      • SELECT EXISTS(SELECT 1 FROM test WHERE text LIKE '%something%'): 0.87045907974243s。
      • SELECT EXISTS(SELECT 1 FROM test WHERE text LIKE '%something%' LIMIT 1) : 0.044898986816406s。

      但是现在,有了 BIGINT PK 字段,只有一个条目等于 '321321':

      • SELECT * FROM test2 WHERE id ='321321' LIMIT 1mysql_num_rows() : 0.0089840888977051s。
      • SELECT count(*) as count FROM test2 WHERE id ='321321' : 0.00033879280090332s。
      • SELECT EXISTS(SELECT 1 FROM test2 WHERE id ='321321') : 0.00023889541625977s。
      • SELECT EXISTS(SELECT 1 FROM test2 WHERE id ='321321' LIMIT 1):0.00020313262939453s。 (更快)

      【讨论】:

      • 感谢您的额外回答。您是否发现 TEXT 字段的两个最快选项之间的时间差异非常一致?差异似乎不大,在这两种情况下使用 SELECT EXISTS(SELECT 1 ... LIMIT 1) 似乎都很好。
      • 你是对的,关于文本字段的其他结果,差异并不那么重要。尽管如此,使用SELECT 1 FROM test WHERE texte LIKE '%something%' LIMIT 1 查询可能会更好
      • 我在mysql上试过,如果你用select 1 ... limit 1,用select exists包围也没用
      • @LittleNooby 有区别。 SELECT EXISTS ... 给出真值和假值(1 或 0),而 SELECT 1 ... 给出 1 或空值。根据您的情况,false 值和空集之间存在细微差别。
      • 您使用的是哪个版本的 MySQL?至少在 5.5+ 中,EXISTS (SELECT ...)EXISTS (SELECT ... LIMIT 1) 之间没有区别。 MySQL 很聪明,可以自己插入这个LIMIT 1,因为EXISTS 就是这样工作的:当找到至少一个结果时它会停止。
      【解决方案7】:

      建议您不要使用Count,因为count 总是会为db 使用SELECT 1 带来额外的负载,如果您的记录在那里,它会返回1,否则它会返回null,您可以处理它。

      【讨论】:

        【解决方案8】:

        @ChrisThompson 回答的简短示例

        示例:

        mysql> SELECT * FROM table_1;
        +----+--------+
        | id | col1   |
        +----+--------+
        |  1 | foo    |
        |  2 | bar    |
        |  3 | foobar |
        +----+--------+
        3 rows in set (0.00 sec)
        
        mysql> SELECT EXISTS(SELECT 1 FROM table_1 WHERE id = 1);
        +--------------------------------------------+
        | EXISTS(SELECT 1 FROM table_1 WHERE id = 1) |
        +--------------------------------------------+
        |                                          1 |
        +--------------------------------------------+
        1 row in set (0.00 sec)
        
        mysql> SELECT EXISTS(SELECT 1 FROM table_1 WHERE id = 9);
        +--------------------------------------------+
        | EXISTS(SELECT 1 FROM table_1 WHERE id = 9) |
        +--------------------------------------------+
        |                                          0 |
        +--------------------------------------------+
        1 row in set (0.00 sec)
        

        使用别名:

        mysql> SELECT EXISTS(SELECT 1 FROM table_1 WHERE id = 1) AS mycheck;
        +---------+
        | mycheck |
        +---------+
        |       1 |
        +---------+
        1 row in set (0.00 sec)
        

        【讨论】:

        • 虽然接受的答案很有帮助,但我很欣赏别名语法。谢谢!
        【解决方案9】:

        或者您可以在条件中插入原始 sql 部分 所以我有 'conditions'=>array('Member.id NOT IN (SELECT Membership.member_id FROM members AS Membership)')

        【讨论】:

          【解决方案10】:

          有时获取行的自动递增主键 (id)(如果存在)和 0(如果不存在)非常方便。

          这是如何在单个查询中完成的:

          SELECT IFNULL(`id`, COUNT(*)) FROM WHERE ...
          

          【讨论】:

          • 为什么不在这里使用IFNULL(id, 0) 而不是COUNT(*)
          【解决方案11】:

          在我的研究中,我可以找到跟随速度的结果。

          select * from table where condition=value
          (1 total, Query took 0.0052 sec)
          
          select exists(select * from table where condition=value)
          (1 total, Query took 0.0008 sec)
          
          select count(*) from table where condition=value limit 1) 
          (1 total, Query took 0.0007 sec)
          
          select exists(select * from table where condition=value limit 1)
          (1 total, Query took 0.0006 sec) 
          

          【讨论】:

          • 除非你完全控制了宇宙,否则这些数字毫无意义。一方面,尝试以相反的顺序进行操作。当然,除非你的观点是没有区别。在那种情况下,你可能是对的。
          【解决方案12】:

          我觉得值得指出的是,尽管在 cmets 中有所提及,但在这种情况下:

          SELECT 1 FROM my_table WHERE *indexed_condition* LIMIT 1
          

          优于:

          SELECT * FROM my_table WHERE *indexed_condition* LIMIT 1
          

          这是因为索引可以满足第一个查询,而第二个查询需要行查找(除非可能所有表的列都在使用的索引中)。

          添加LIMIT 子句允许引擎在找到任何行后停止。

          第一个查询应该类似于:

          SELECT EXISTS(SELECT * FROM my_table WHERE *indexed_condition*)
          

          向引擎发送相同的信号(1/* 在这里没有区别),但我仍然会写 1 以加强使用 EXISTS 时的习惯:

          SELECT EXISTS(SELECT 1 FROM my_table WHERE *indexed_condition*)
          

          如果在没有行匹配时需要显式返回,则添加 EXISTS 包装可能有意义。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-03-23
            • 2012-02-20
            • 1970-01-01
            • 1970-01-01
            • 2010-09-29
            • 1970-01-01
            相关资源
            最近更新 更多