解决MySQL中IN子查询会导致无法使用索引问题

测试表如下:

CREATE TABLE `test_table` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pay_id` int(11) DEFAULT NULL,
  `pay_time` datetime DEFAULT NULL,
  `other_col` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_pay_id` (`pay_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

第一步:建一个存储过程插入测试数据,测试数据的特点是pay_id可重复,这里在存储过程处理成,循环插入300W条数据的过程中,每隔100条数据插入一条重复的pay_id,时间字段在一定范围内随机

存储过程:执行 call test_insert(3000000); 插入303000行数据

CREATE DEFINER=`root`@`%` PROCEDURE `test_insert`(IN `loopcount` INT)
    COMMENT '往test_insert表中插入数据'
BEGIN
  declare cnt int;
  set cnt = 0;
  while cnt< loopcount do
    insert into test_table (pay_id,pay_time,other_col) values (cnt,date_add(now(), interval floor(300*rand()) day),uuid());
    if (cnt mod 100 = 0) then
      insert into test_table (pay_id,pay_time,other_col) values (cnt,date_add(now(), interval floor(300*rand()) day),uuid());
    end if;
    set cnt = cnt + 1;  
  end while;

END

 

两种子查询的写法:查询某个时间段之内的业务Id大于1的数据

方法一:IN子查询中是某段时间内业务统计大于1的业务Id,外层按照IN子查询的结果进行查询,业务Id的列pay_id上有索引,逻辑也比较简单,这种写法,在数据量大的时候确实效率比较低,用不到索引

注:In子查询的执行计划,发现外层查询是一个全表扫描的方式,没有用到pay_id上的索引,如果想对第一种查询方式使用强制索引,虽然是不报错的,但是发现根本没用,如果子查询是直接的值,则是可以正常使用索引的。

SELECT * FROM test_table2 FORCE INDEX (idx_pay_id)
WHERE
    pay_id IN (
        SELECT pay_id
        FROM test_table2
        WHERE pay_time >= "2019-06-01 00:00:00" AND pay_time <= "2020-07-03 12:59:59"
        GROUP BY pay_id
        HAVING count(pay_id) > 1
    );

 

方法二:与子查询进行join关联,这种写法相当于上面的IN子查询写法,下面测试发现,效率确实有不少的提高

注:join自查的执行计划,外层(tpp1别名的查询)是用到pay_id上的索引的。

SELECT tpp1.* FROM test_table2 tpp1, 
(
   SELECT pay_id 
   FROM test_table2 
   WHERE pay_time>="2019-07-01 00:00:00"
   AND pay_time<="2020-07-03 12:59:59"
   GROUP BY pay_id 
   HAVING count(pay_id) > 1
) tpp2 
WHERE tpp1.pay_id=tpp2.pay_id;

 

方法三:加一个使用临时表的情况,虽然比不少join方式查询的,但是也比直接使用IN子查询效率要高,这种情况下,也是可以使用到索引的,不过这种简单的情况,是没有必要使用临时表的。

DROP TABLE IF EXISTS t_pay_id;
CREATE TEMPORARY TABLE t_pay_id(pay_id INT);

INSERT INTO t_pay_id
SELECT pay_id FROM test_table2
WHERE pay_time>="2019-07-01 00:00:00" AND pay_time<="2020-07-03 12:59:59"
GROUP BY pay_id 
HAVING count(pay_id) > 1;

SELECT * FROM test_table2
WHERE pay_id IN (SELECT pay_id FROM t_pay_id);

 

 

 

posted @ 2019-11-14 16:03  liuweipcs  阅读(804)  评论(0)    收藏  举报