mysql5.7决定SQL中IN条件是否走索引的成本计算,mysql中的index dive是在两个区间之间计算有多少条记录的方式

一、表和索引设计

CREATE TABLE `t_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'primary key',
  `username` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'user name',
  `age` int(4) NOT NULL DEFAULT 20 COMMENT 'user age',
  `birthday_date_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'user birthday',
  `address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `remark` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'remark something',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
  `version` int(4) NOT NULL DEFAULT 0 COMMENT 'update version',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `idx_name`(`username`) USING BTREE,
  INDEX `idx_age_remark`(`age`, `remark`) USING BTREE,
  INDEX `idx_create_time`(`create_time`) USING BTREE,
  INDEX `idx_address`(`address`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10003 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = DYNAMIC;

二、mysql预估表行数,和索引不重复行数

2.1)mysql预估t_user表行数 9822行:

2.2)mysql预估t_user表各个索引不重复数据行数:show index from t_user;

Non_unique : 是否为唯一索引
Key_name : 索引名称
Seq_in_index : 索引列顺序,如果是联合索引,就会有数字排序123这样子,其他单个列索引都是1
Cardinality : 索引基数。估算的该索引列的不重复值数据行数,所以如果该值和表行数数据一样,说明该索引列没有重复值。
Index_type : 索引类型,一般是BTREE索引

三、执行成本计算

【index dive】

在两个区间之间计算有多少条记录的方式,在mysql中被称为index dive。如果一个SQL 用了 IN (2万个参数,或者一个子查询SQL结果集非常多的),那么mysql很有可能认为走全表扫描更快。
查看mysql的index dive参数值:

SHOW VARIABLES LIKE '%dive%';  -- 默认 eq_range_index_dive_limit = 200。在5.7.3版本以前,mysql这个值的设置是10


也就是当IN条件里的参数个数不超过200时,mysql才走index dive,去精确统计有多少行数,超过200个了,myslq会使用索引统计数据进行估算。那怎么看index dive会走索引还是全表扫描呢?看以下这个SQL例子。

【SQL例子】

EXPLAIN SELECT * FROM t_user WHERE username in ('10569d48-a94d-4ee4-831d-261b3777bd1f', 'e33a68d2-78af-497e-87e0-24516e73952fs');

执行成本 = address的索引列不重复行数预估数 1000 / 表预估总行数 9822 * 假设in条件里是2万个 20000 = 2036.24 

参考index dive的默认设置是200,这里计算出的执行成本2036.24就太大了,mysql决定走全表扫描。

四、mysql对in条件查询做了优化

1)物化临时表方法。select * from t1 where m1 in ( select m2 from t2 where m2 = 'a' );   如果结果集过大,mysql会根据temp_table_size参数去做物化,即建立临时表,至于是内存临时表,还是磁盘临时表,mysql要看数据大小。 加入建立的临时表叫t_temp,那mysql会将此sql优化成二表连查: select t1.* from t1, t_temp on t1.id = t_temp.id;

2)Semi-join半连接。对于in条件查询,mysql的SQL优化器会做3种优化:

    2.1 表上拉。比如子查询里面的=条件就是唯一索引,只会找到一条记录,那么就是等价于inner join

    2.2 重复值消除。 mysql会建立一个临时表去重

    2.3 松散扫描。在子查询中,如果使用了一个索引列去查询,只需要扫描二级索引但是只取值相同的第一条到子查询外部去匹配的方式,就叫松散扫描。

3)所有的in条件查询都可以转化为exists查询,这样还有可能走索引。

end.

posted on 2021-07-04 17:26  梦幻朵颜  阅读(876)  评论(0编辑  收藏  举报