数据库
首页 > 数据库> > EXISTS vs IN-在MySQL 5.5和MySQL 5.7中哪个更好?

EXISTS vs IN-在MySQL 5.5和MySQL 5.7中哪个更好?

作者:互联网

我正在MySQL 5.7和更高版本上测试两种不同的SQL方法(EXISTS与IN).从性能角度来看5.5.
关于测试的一个附带说明,两个数据库都位于同一台计算机上,对于每个测试,我在其中仅激活了其中一个数据库.它们每个都分配有4GB的内存.我在每次测试之前都要重新启动数据库,以确保没有进行缓存(至少不是在数据库级别).

我在StackOverflow上看到了很多问题,从IN到EXISTS的转换对性能很有帮助.在大多数线程中,较早的MySQL版本(低于5.6版)就是这种情况.因此,我的首要目标是检验这一理论(对于较旧的MySQL版本,EXISTS比IN更好).

另外,我一直在阅读IN可能已经在新的MySQL版本中得到了改进,所以我想亲自看看.

因此,为了更好地理解哪种查询更适合将来的查询,我运行了以下测试:

定义常数:

SET @quantity = 50;

存在查询:

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-IN-48秒
> MySQL 5.7-存在-46秒
> MySQL 5.7-IN-4秒

如您所见,结果令人惊讶:

>由于某种原因,EXISTS替代方案的性能比MySQL 5.5的IN替代方案差.
>对于MySQL 5.7,即使EXISTS替代的EXPLAIN看起来比IN替代“看起来更好”,但IN的性能似乎明显好于EXISTS(请参见下面的EXPLAIN输出).

您能分享您的想法吗?

当您要编写新查询时,如何在IN和EXISTS之间进行选择?您将如何指导您的团队?我们每次都可以尝试使用它们,但是听起来有些嘲笑,并且在编写复杂的查询时可能会浪费大量时间.对于MySQL来说,应该有一些准则来指导它们何时更好. 5.6和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的执行总是比存在的要差,那么优化器可以采取的第一个简单步骤就是简单地重写in中的每个in.

in使优化器可以利用您无法针对一般存在的子查询执行的several different execution paths.它尤其可以在存在时执行(反之亦然).因此,如果您希望有一个通用的准则,则可以在任何可能的地方使用,因为它可以被简单地重写为存在,从而使您可以选择使用哪种方式(和编译器)来进行操作.如果测试表明MySQL采取了错误的方法,则可以简单地切换到存在,迫使优化器执行相同的操作.

如果优化器选择采用那些新近可用的执行计划之一,那么它们可能会变得更快-或没有.优化程序做出的许多决定都是如此:它基本上是基于关于数据的有限信息来猜测的,并且可能会猜错.告诉优化器探索一些不同路径的直接方法是利用Optimizer Hints.稍微更改查询(例如切换到存在)可以使优化器也选择不同的执行计划(例如,因为其他功能不再可用),因此您可能会认为它是间接提示,尽管它比实际提示的可控性差.

这些结果可能会为您带来更快的结果-或出于相同的原因,相反.通常,这取决于您的实际数据和情况.您只需要针对您的特定情况进行测试,然后选择速度更快的一种就正确了.但是请记住,情况可能会发生变化(如果您的数据分布发生变化),因此您可能需要在某些时候重新测试并可能重写查询.

但这通常不会适用-正如您已经意识到的,对于您的特定情况,您认为“对于较旧的MySQL版本,EXISTS优于IN的假设”不成立,而对于您研究的大多数问题似乎都适用. (可能是也可能不是有偏见的选择).

在完成一般性介绍之后(您想听听一些想法,所以就得到了一些想法):

for 5.7的in性能如此之好的原因是,MySQL在可能的执行计划中找到了一种对您的特定数据分发非常有效的方式.

假设您只有1个ss_quantity>客户. @数量.由于您有ss_quantity的索引,因此对查询的最快回答是仅使用此索引,使用该数量查找客户即可完成.您拥有该数量值的客户越多,获得的效果就越差.例如.假设每个客户都满足数量条件,则最好使用一个以您的订单为依据的索引(并以此为限)-MySQL 5.5通过选择利用索引customer_index_1的执行计划来决定要做的事情.

存在于使MySQL找到该路径的更改.在5.5之间,优化器的性能要好得多.和5.7.因此,这不仅仅是随机的运气.但是,如果您的数据分发不会超出临界点,而MySQL仍会采用这种方式,它将变得更慢.达到收支平衡点的客户数量将非常惊人.您显然站在这一点的有利方面.

一种测试方法是将@quantity设置为较低的值.您可能会发现一个像in存在一样执行in的值,甚至可能存在比in存在更快的值.另一个因素是limit的值.应该像当前存在的那样执行limit 1(假设查询返回的行数要多一些),因此您可能会找到一些数量参数,并限制in的速度比现有速度慢.如果MySQL确实将in的执行计划更改为与存在相似,则在不存在的情况下会有一些limit的值(我们知道,至少对于1000).您可能会发现其中in的值再次慢于现有值的值.

但是要再次强调一点:这通常不适用.这些值将取决于您的数据,情况可能随之改变.如果您例如越来越多的客户,限制1000可能会变得越来越不相关,并且将来您可能会达到临界点,即in变得比现有情况差(MySQL无法实现),并且可能必须更改查询.

标签:performance,database-performance,sql,mysql
来源: https://codeday.me/bug/20191025/1927921.html