为啥带有'exists'的sql运行速度比使用MySQL的'in'慢

Posted

技术标签:

【中文标题】为啥带有\'exists\'的sql运行速度比使用MySQL的\'in\'慢【英文标题】:why sql with 'exists' run slower than 'in' using MySQL为什么带有'exists'的sql运行速度比使用MySQL的'in'慢 【发布时间】:2013-12-08 23:08:12 【问题描述】:

我是 mysql 优化的新手,我发现了一个惊人的事情:带有 'exists' 的 sql 运行速度比使用 'in' 慢!!!

以下是我的 DDL:

mysql> `show create table order\G`;
*************************** 1. row ***************************
       Table: order
Create Table: CREATE TABLE `order` (
  `id` int(4) NOT NULL AUTO_INCREMENT,
  `employee_id` int(4) NOT NULL,
  `price` decimal(7,2) NOT NULL,
  `trade_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `fk_employee_id` (`employee_id`),
  CONSTRAINT `fk_employee_id` FOREIGN KEY (`employee_id`) REFERENCES `employee` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=231001 DEFAULT CHARSET=utf8



mysql> `show create table order_detail\G`;
*************************** 1. row ***************************
       Table: order_detail
Create Table: CREATE TABLE `order_detail` (
  `id` int(4) NOT NULL AUTO_INCREMENT,
  `menu_id` int(4) NOT NULL,
  `order_id` int(4) NOT NULL,
  `amount` int(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_menu_id` (`menu_id`),
  KEY `fk_order_id` (`order_id`),
  CONSTRAINT `fk_menu_id` FOREIGN KEY (`menu_id`) REFERENCES `menu` (`id`),
  CONSTRAINT `fk_order_id` FOREIGN KEY (`order_id`) REFERENCES `order` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1037992 DEFAULT CHARSET=utf8



Query Solution 1: use exists
---------------------------------

mysql> `SELECT count(`order`.id) FROM `order` WHERE EXISTS ( SELECT 1 FROM order_detail WHERE order_detail.order_id = `order`.id GROUP BY (order_detail.order_id) HAVING COUNT(order_id) > 5 );`
+-------------------+
| count(`order`.id) |
+-------------------+
|             92054 |
+-------------------+
1 row in set (***6.53 sec***)

mysql> `explain SELECT count(`order`.id) FROM `order` WHERE EXISTS ( SELECT 1 FROM order_detail WHERE order_detail.order_id = `order`.id GROUP BY (order_detail.order_id) HAVING COUNT(order_id) > 5 )\G;`
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: order
         type: index
possible_keys: NULL
          key: fk_employee_id
      key_len: 4
          ref: NULL
         rows: 231032
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 2
  select_type: DEPENDENT SUBQUERY
        table: order_detail
         type: ref
possible_keys: fk_order_id
          key: fk_order_id
      key_len: 4
          ref: performance_test.order.id
         rows: 3
        Extra: Using where; Using index
2 rows in set (0.01 sec)


Query solution 2: use in
------------------------

mysql> `SELECT count(`order`.id) FROM `order` WHERE `order`.id IN ( SELECT order_detail.order_id FROM order_detail GROUP BY (order_detail.order_id) HAVING COUNT(order_id) > 5 ) ;`
+-------------------+
| count(`order`.id) |
+-------------------+
|             92054 |
+-------------------+
1 row in set (***3.88 sec***)

mysql> `explain SELECT count(`order`.id) FROM `order` WHERE `order`.id IN ( SELECT order_detail.order_id FROM order_detail GROUP BY (order_detail.order_id) HAVING COUNT(order_id) > 5 ) \G;`<br>
*************************** 1. row ***************************
           id: 1
  select_type: PRIMARY
        table: order
         type: index
possible_keys: NULL
          key: fk_employee_id
      key_len: 4
          ref: NULL
         rows: 231032
        Extra: Using where; Using index
*************************** 2. row ***************************
           id: 2
  select_type: SUBQUERY
        table: order_detail
         type: index
possible_keys: fk_order_id
          key: fk_order_id
      key_len: 4
          ref: NULL
         rows: 1036314
        Extra: Using index
2 rows in set (0.00 sec)

【问题讨论】:

这是一个测试项目,如果你想重现这种情况,我可以导出测试数据并分享它 我认为问题出在依赖子查询上 您是否尝试过颠倒执行顺序:在 IN 之前和 EXIST 之后,可能是第二个查询使用的缓存执行计划(来自第一个查询)。 我认为 EXISTS 查询在外部查询上执行 JOIN,因此从数据库中读取的数据更多 (sqlmag.com/database-performance-tuning/advanced-join-techniques)。在使用索引过滤外部查询之后,IN 查询首先执行子查询(读取所有记录)。 我相信你需要阅读其他类似的帖子,这里有一个***.com/questions/3999600/…它很详细 【参考方案1】:

我认为你有点困惑,你有一个错误的想法,'EXISTS' 比'IN' 工作得更快,我试图让你理解原因..

EXISTS 返回一个布尔值,并将在第一次匹配时返回一个布尔值。因此,如果您正在处理重复/倍数,“EXISTS”将比“IN”或“JOIN”更快地执行,具体取决于数据和需求。

而“IN”是 OR 子句的语法糖。虽然它非常适合,但在处理该比较的大量值时存在问题(1,000 以北)。在重复/倍数的情况下,“IN”检查所有存在的值,这自然会比“EXISTS”消耗更多的执行时间,这就是为什么“IN”总是比“EXISTS”慢。

我希望我能澄清你的困惑。

【讨论】:

感谢您的回复,我在网上搜索了更多资料,也许本质区别不是“存在”和“输入”,而是“分组依据”对子查询的影响,因为我有使用'Group By'子句,所以'Exists'的快捷方式不存在,这两个查询都必须全扫描表或索引。我喜欢这两个查询的解释输出有点不同,Select_Type的'Exists' 中的 SubQuery 是 'Dependent Subquery',但另一个是 'SubQuery'【参考方案2】:

如果父集和子集都很大,'EXISTS' 可以比'IN' 工作得更快。

因为对于大型集合,O(n*log(n)) 比 O(n*n) 快

如果子集很小,'EXISTS' 的工作速度可能比 'IN' 慢。

由于查询结构和由此产生的查询计划更改;例如慢 30%(1.5s)。

【讨论】:

以上是关于为啥带有'exists'的sql运行速度比使用MySQL的'in'慢的主要内容,如果未能解决你的问题,请参考以下文章

为啥NOT IN比NOT EXISTS效率差

为啥此 SIMD 代码运行速度比等效标量慢?

为啥我的 Spark 运行速度比纯 Python 慢?性能比较

Google Colab 运行速度比 Jupyterlab 快,Google Colab 为啥以及如何运行?

为啥 wkwebview 在科尔多瓦的运行速度比移动 safari 慢?

为啥在这段代码中 CPU 运行速度比 GPU 快?