【发布时间】:2018-02-21 13:08:45
【问题描述】:
从性能角度来看,我正在 MySQL 5.7 和 5.5 上测试两种不同的 SQL 方法(EXISTS 与 IN)。 作为测试的旁注,两个数据库都在同一台机器上,我为每个测试只激活了其中一个。他们每个人都有 4GB 的内存分配给他们。我在每次测试之前都重新启动了数据库,以确保没有完成缓存(至少不在数据库级别)。
我在 StackOverflow 上看到了很多问题,从 IN 到 EXISTS 的转换在性能方面很有帮助。在大多数线程中,旧 MySQL 版本(ver
另外,我一直在读到 IN 可能在较新的 MySQL 版本中得到了改进,所以我想亲眼看看。
因此,为了更好地了解哪一个更适合我未来的查询,我进行了以下测试:
定义常数:
SET @quantity = 50;
EXISTS 查询:
SELECT SQL_NO_CACHE
c.c_first_name, c.c_birth_country
FROM
customer c
WHERE
EXISTS( SELECT
1
FROM
store_sales ss
WHERE
ss.ss_quantity > @quantity
AND ss.ss_customer_sk = c.c_customer_sk)
ORDER BY c.c_first_name DESC , c.c_birth_country DESC
LIMIT 1000;
等效的 IN 查询:
SELECT SQL_NO_CACHE
c.c_first_name, c.c_birth_country
FROM
customer c
WHERE
c.c_customer_sk IN (SELECT
ss.ss_customer_sk
FROM
store_sales ss
WHERE
ss.ss_quantity > @quantity)
ORDER BY c.c_first_name DESC , c.c_birth_country DESC
LIMIT 1000;
结果:
- MySQL 5.5 - 存在 - 53 秒
MySQL 5.5 - 输入 - 48 秒
MySQL 5.7 - 存在 - 46 秒
- MySQL 5.7 - 输入 - 4 秒
如您所见,结果有点令人惊讶:
- 由于某种原因,EXISTS 替代方案的性能比 MySQL 5.5 的 IN 替代方案差。
- 对于 MySQL 5.7,IN 的性能似乎比 EXISTS 好得多,尽管 EXISTS 替代方案的 EXPLAIN 比 IN 替代方案“看起来更好”(请参阅下面的 EXPLAIN 输出)。
你能分享一下你的想法吗?
当您准备编写新查询时,您如何在 IN 和 EXISTS 之间进行选择?你将如何指导你的团队?我们每次都可以尝试它们,但这听起来有点可笑,并且在编写复杂的查询时会浪费很多时间。应该有一些指导方针来确定它们何时更好,对于 MySQL 5.6。
我缺少任何来自 MySQL 的文档吗?
只是用所有相关表格结束循环并解释信息 -
带有索引的表创建脚本(您可能还会在此处看到许多无用或冗余的索引,但请忽略它们,因为这是一个测试环境):
CREATE TABLE `customer` (
`c_customer_sk` int(11) NOT NULL,
`c_customer_id` char(16) NOT NULL,
`c_current_cdemo_sk` int(11) DEFAULT NULL,
`c_current_hdemo_sk` int(11) DEFAULT NULL,
`c_current_addr_sk` int(11) DEFAULT NULL,
`c_first_shipto_date_sk` int(11) DEFAULT NULL,
`c_first_sales_date_sk` int(11) DEFAULT NULL,
`c_salutation` char(10) DEFAULT NULL,
`c_first_name` char(20) DEFAULT NULL,
`c_last_name` char(30) DEFAULT NULL,
`c_preferred_cust_flag` char(1) DEFAULT NULL,
`c_birth_day` int(11) DEFAULT NULL,
`c_birth_month` int(11) DEFAULT NULL,
`c_birth_year` int(11) DEFAULT NULL,
`c_birth_country` varchar(20) DEFAULT NULL,
`c_login` char(13) DEFAULT NULL,
`c_email_address` char(50) DEFAULT NULL,
`c_last_review_date` char(10) DEFAULT NULL,
PRIMARY KEY (`c_customer_sk`),
KEY `c_fsd2` (`c_first_shipto_date_sk`),
KEY `c_fsd` (`c_first_sales_date_sk`),
KEY `c_hd` (`c_current_hdemo_sk`),
KEY `c_cd` (`c_current_cdemo_sk`),
KEY `c_a` (`c_current_addr_sk`),
KEY `customer_index_1` (`c_first_name`,`c_birth_country`),
CONSTRAINT `c_a` FOREIGN KEY (`c_current_addr_sk`) REFERENCES `customer_address` (`ca_address_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `c_cd` FOREIGN KEY (`c_current_cdemo_sk`) REFERENCES `customer_demographics` (`cd_demo_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `c_fsd` FOREIGN KEY (`c_first_sales_date_sk`) REFERENCES `date_dim` (`d_date_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `c_fsd2` FOREIGN KEY (`c_first_shipto_date_sk`) REFERENCES `date_dim` (`d_date_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `c_hd` FOREIGN KEY (`c_current_hdemo_sk`) REFERENCES `household_demographics` (`hd_demo_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `store_sales` (
`ss_sold_date_sk` int(11) DEFAULT NULL,
`ss_sold_time_sk` int(11) DEFAULT NULL,
`ss_item_sk` int(11) NOT NULL,
`ss_customer_sk` int(11) DEFAULT NULL,
`ss_cdemo_sk` int(11) DEFAULT NULL,
`ss_hdemo_sk` int(11) DEFAULT NULL,
`ss_addr_sk` int(11) DEFAULT NULL,
`ss_store_sk` int(11) DEFAULT NULL,
`ss_promo_sk` int(11) DEFAULT NULL,
`ss_ticket_number` int(11) NOT NULL,
`ss_quantity` int(11) DEFAULT NULL,
`ss_wholesale_cost` decimal(7,2) DEFAULT NULL,
`ss_list_price` decimal(7,2) DEFAULT NULL,
`ss_sales_price` decimal(7,2) DEFAULT NULL,
`ss_ext_discount_amt` decimal(7,2) DEFAULT NULL,
`ss_ext_sales_price` decimal(7,2) DEFAULT NULL,
`ss_ext_wholesale_cost` decimal(7,2) DEFAULT NULL,
`ss_ext_list_price` decimal(7,2) DEFAULT NULL,
`ss_ext_tax` decimal(7,2) DEFAULT NULL,
`ss_coupon_amt` decimal(7,2) DEFAULT NULL,
`ss_net_paid` decimal(7,2) DEFAULT NULL,
`ss_net_paid_inc_tax` decimal(7,2) DEFAULT NULL,
`ss_net_profit` decimal(7,2) DEFAULT NULL,
PRIMARY KEY (`ss_item_sk`,`ss_ticket_number`),
KEY `ss_s` (`ss_store_sk`),
KEY `ss_t` (`ss_sold_time_sk`),
KEY `ss_d` (`ss_sold_date_sk`),
KEY `ss_p` (`ss_promo_sk`),
KEY `ss_hd` (`ss_hdemo_sk`),
KEY `ss_c` (`ss_customer_sk`),
KEY `ss_cd` (`ss_cdemo_sk`),
KEY `ss_a` (`ss_addr_sk`),
KEY `store_sales_index_1` (`ss_quantity`,`ss_customer_sk`),
KEY `store_sales_idx_sk_price` (`ss_item_sk`,`ss_sales_price`),
KEY `store_sales_idx_price_sk` (`ss_sales_price`,`ss_item_sk`),
CONSTRAINT `ss_a` FOREIGN KEY (`ss_addr_sk`) REFERENCES `customer_address` (`ca_address_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_c` FOREIGN KEY (`ss_customer_sk`) REFERENCES `customer` (`c_customer_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_cd` FOREIGN KEY (`ss_cdemo_sk`) REFERENCES `customer_demographics` (`cd_demo_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_d` FOREIGN KEY (`ss_sold_date_sk`) REFERENCES `date_dim` (`d_date_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_hd` FOREIGN KEY (`ss_hdemo_sk`) REFERENCES `household_demographics` (`hd_demo_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_i` FOREIGN KEY (`ss_item_sk`) REFERENCES `item` (`i_item_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_p` FOREIGN KEY (`ss_promo_sk`) REFERENCES `promotion` (`p_promo_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_s` FOREIGN KEY (`ss_store_sk`) REFERENCES `store` (`s_store_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `ss_t` FOREIGN KEY (`ss_sold_time_sk`) REFERENCES `time_dim` (`t_time_sk`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8
说明 - MySQL 5.7 - EXISTS 替代方案 -
1 PRIMARY c index customer_index_1 124 1000 100.00 Using where; Using index
2 DEPENDENT SUBQUERY ss ref ss_c,store_sales_index_1 ss_c 5 tpcds.c.c_customer_sk 32 50.00 Using where
说明 - MySQL 5.7 - IN 替代方案 -
1 SIMPLE ss range ss_c,store_sales_index_1 store_sales_index_1 5 1395022 100.00 Using where; Using index; Using temporary; Using filesort; Start temporary
1 SIMPLE c eq_ref PRIMARY PRIMARY 4 tpcds.ss.ss_customer_sk 1 100.00 End temporary
说明 - MySQL 5.5 - EXISTS 替代方案 -
1 PRIMARY c index customer_index_1 124 1000 Using where; Using index
2 DEPENDENT SUBQUERY ss ref ss_c,store_sales_index_1 ss_c 5 tpcds.c.c_customer_sk 14 Using where
说明 - MySQL 5.5 - IN 替代方案 -
1 PRIMARY c index customer_index_1 124 1000 Using where; Using index
2 DEPENDENT SUBQUERY ss index_subquery ss_c,store_sales_index_1 ss_c 5 func 14 Using where
【问题讨论】:
-
注意:IN 和 EXISTS 之间存在细微的语义差异(wrt 处理 NULL)。选择正确的而不是最快的。
-
@wildplasser,谢谢。在这个特定的问题和测试中,我忽略了这种差异,因为在许多情况下 NULL 是无关紧要的。
-
就性能而言,您可以尝试使用 INNER JOIN 吗?我一直听说它更好。
-
@DanielE。内部联接的问题在于,由于联接的笛卡尔性质,在许多情况下它会返回不同的结果。因此,它将需要某种技巧,例如应用 DISTINCT 来减少返回的结果数量(这可能会减慢查询速度)。因此,在这个测试中,我将重点放在 IN 与 EXISTS 上,也许将来我会将测试扩展到内连接。
-
" SELECT SQL_NO_CACHE c.c_first_name, c.c_birth_country FROM customer c INNER JOIN (SELECT ss.ss_customer_sk FROM store_sales ss WHERE ss.ss_quantity > @quantity) t ON t.ss_customer_sk = c.c_customer_sk ORDER BY c.c_first_name DESC , c.c_birth_country DESC " 会返回不同的结果吗?
标签: mysql sql performance database-performance