Mysql使用IN功能相关与性能对比

Posted by chunpat on October 7, 2019

问题概述

平时Mysql喜欢先sql语句找出id字符串,然后用in后面接(id,id,……)去查找数据。查阅相关文章,说sql语句长度有限制,随着数据增长就不能执行,但是 这个长度却很大,一般可以不用考虑。

查看方法。

mysql>show variables like '%max_allowed_pack%'
+--------------------------+-----------------+
| Variable_name            | Value           |
+--------------------------+-----------------+
| max_allowed_packet       | 1073741824      |
| slave_max_allowed_packet | 1073741824      |
+--------------------------+-----------------+
返回行数:[2],耗时:7 ms.

根据返回结果可以看出,这个最大长度是很大的。

IN相关操作和替代方法对比

1、IN后面接主键字符串( 概念错误SELECT GROUP_CONCAT(order_id,’’) FROM order_action WHERE NAME = ‘user_add’ 查找出来的并不是字符串,而是数据集 )

代码

EXPLAIN SELECT
	* 
FROM
	`order` 
WHERE
	id IN ( SELECT GROUP_CONCAT(order_id,'') FROM order_action WHERE NAME = 'user_add' )

结果显示,order表需要找全部行,order_action也是。

+----+--------------------+--------------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type        | table        | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+--------------------+--------------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | PRIMARY            | order        | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 17239 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | order_action | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 20660 |    10.00 | Using where |
+----+--------------------+--------------+------------+------+---------------+------+---------+------+-------+----------+-------------+
2 rows in set, 1 warning (0.01 sec)

2、in 后面接 select 数据集

代码

EXPLAIN SELECT
	* 
FROM
	`order` 
WHERE
	id IN ( SELECT order_id FROM order_action WHERE NAME = 'user_add' )

结果显示,会创建临时表,order表只需要查1rows,order_action表查所有行。

+----+--------------+--------------+------------+--------+---------------+---------+---------+----------------------+-------+----------+-------------+
| id | select_type  | table        | partitions | type   | possible_keys | key     | key_len | ref                  | rows  | filtered | Extra       |
+----+--------------+--------------+------------+--------+---------------+---------+---------+----------------------+-------+----------+-------------+
|  1 | SIMPLE       | <subquery2>  | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                 |  NULL |   100.00 | NULL        |
|  1 | SIMPLE       | order        | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | <subquery2>.order_id |     1 |   100.00 | NULL        |
|  2 | MATERIALIZED | order_action | NULL       | ALL    | NULL          | NULL    | NULL    | NULL                 | 20649 |    10.00 | Using where |
+----+--------------+--------------+------------+--------+---------------+---------+---------+----------------------+-------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)

3、用 join 替换 in

代码

EXPLAIN SELECT
	* 
FROM
	`order` AS ot
	INNER JOIN order_action oat ON oat.order_id = ot.id 
	AND oat.NAME = 'user_add'

结果显示,不创建临时表,但order表只需要查1rows,order_action表查所有行。

+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+-------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys | key     | key_len | ref               | rows  | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+-------+----------+-------------+
|  1 | SIMPLE      | oat   | NULL       | ALL    | NULL          | NULL    | NULL    | NULL              | 20653 |    10.00 | Using where |
|  1 | SIMPLE      | ot    | NULL       | eq_ref | PRIMARY       | PRIMARY | 4       | sima.oat.order_id |     1 |   100.00 | NULL        |
+----+-------------+-------+------------+--------+---------------+---------+---------+-------------------+-------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

4、exist 替代 in (效率不行)

代码

EXPLAIN SELECT
	* 
FROM
	`order` AS ot 
WHERE
	EXISTS ( SELECT order_id FROM order_action oat WHERE ot.id = oat.order_id AND NAME = 'user_add' ) 

结果测试的是两张关联的表都查了所有。所以exist效率不行。

+----+--------------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type        | table | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+--------------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | PRIMARY            | ot    | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 17233 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | oat   | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 20656 |     1.00 | Using where |
+----+--------------------+-------+------------+------+---------------+------+---------+------+-------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

explain 分析 sql 语句的相关属性解析

连接操作的TYPE类型

(1)SYSTEM

CONST的特例,当表上只有一条元组匹配

(2)CONST

WHERE条件筛选后表上至多有一条元组匹配时,比如WHERE ID = 2 (ID是主键,值为2的要么有一条要么没有)

(3)EQ_REF

参与连接运算的表是内表(在代码实现的算法中,两表连接时作为循环中的内循环遍历的对象,这样的表称为内表)。 基于索引(连接字段上存在唯一索引或者主键索引,且操作符必须是“=”谓词,索引值不能为NULL)做扫描,使得对外表的一条元组,内表只有唯一一条元组与之对应。

(4)REF

可以用于单表扫描或者连接。参与连接运算的表,是内表。 基于索引(连接字段上的索引是非唯一索引,操作符必须是“=”谓词,连接字段值不可为NULL)做扫描,使得对外表的一条元组,内表可有若干条元组与之对应。

(5)REF_OR_NULL

类似REF,只是搜索条件包括:连接字段的值可以为NULL的情况,比如 where col = 2 or col is null

(6)RANGE

范围扫描,基于索引做范围扫描,为诸如BETWEEN,IN,>=,LIKE类操作提供支持

(7)INDEX_SCAN

索引做扫描,是基于索引在索引的叶子节点上找满足条件的数据(不需要访问数据文件)

(8)ALL

全表扫描或者范围扫描:不使用索引,顺序扫描,直接读取表上的数据(访问数据文件)

(9)UNIQUE_SUBQUERY

在子查询中,基于唯一索引进行扫描,类似于EQ_REF

(10)INDEX_SUBQUERY

在子查询中,基于除唯一索引之外的索引进行扫描

(11)INDEX_MERGE

多重范围扫描。两表连接的每个表的连接字段上均有索引存在且索引有序,结果合并在一起。适用于作集合的并、交操作。

(12)FT

FULL TEXT,全文检索

结论

实际测试显示,in数据集是最快的。所有优先考虑in数据集这种会创建临时表的方式。

参考


Creative Commons License
本作品采用CC BY-NC-ND 4.0进行许可。转载,请注明原作者 chunpat 及本文源链接。