优化select语句
- 为了使慢速
SELECT ... WHERE
查询更快,首先要检查的是是否可以添加索引。为避免浪费磁盘空间,请构造组合索引。 - 调整函数调用,使函数调用从一多次减少为一次,从而极大地提高了效率。
- 定期使用
ANALYZE TABLE
使表统计信息保持最新 ,为优化器提供构造有效执行计划所需的信息。 - 阅读
EXPLAIN
计划并调整索引。 - 调整MySQL用于缓存的内存区域的大小和属性。
- 避免锁导致的查询性能问题。
1. WHERE子句优化
-
删除不必要的括号。
((a AND b) AND c OR (((a AND b) AND (c AND d)))) -> (a AND b AND c) OR (a AND b AND c AND d)
-
常量折叠。
(a<b AND b=c) AND a=5 -> b>5 AND b=c AND a=5
-
常量条件消除。
(b>=5 AND b=5) OR (b=6 AND 5=5) OR (b=7 AND 5=6) -> b=5 OR b=6
-
使用索引的常量表达式仅计算一次。
-
直接从MyISAM和MEMORY表中检索没有WHERE的单个表上的COUNT(*)。
-
无效的常量表达式。
SELECT * FROM t WHERE a<0 AND a>1; ︶︵︶
-
如果没有使用GROUP BY或者聚合函数(如
COUNT()
,MIN()
)等)就将HAVING字句合并到WHERE字句。SELECT * FROM t WHERE a=1 HAVING b>1; -> SELECT * FROM t WHERE a=1 AND b>1;
-
对于联接中的每个表,构造一个更简单的WHERE以获得表的快速WHERE评估,并尽快跳过行。(复杂的WHERE会延迟每一行数据的过滤时间)
-
在查询的所有表中,优先读取常量表。常量表可以是以下任意一个
-
空表或者只有一行数据的表;
-
与PRIMARY KEY或UNIQUE索引上的WHERE子句一起使用的表,其中所有索引部分都与常量表达式进行比较,并定义为NOT NULL;
扫描二维码关注公众号,回复: 9757394 查看本文章以下所有表均用作常量表:
SELECT * FROM t WHERE primary_key=1; SELECT * FROM t1,t2 WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
-
-
如果
ORDER BY
和GROUP BY
子句中的所有列都来自同一表,则在连接时优先使用该表。 -
ORDER BY
或GROUP BY
包含连接队列中第一个表以外的表中的列,则会创建一个临时表(用于连接后数据集的排序,排序后自动删除)。 -
在索引中的所有列都是数字列的情况下,MySQL甚至可以在不查询数据文件的情况下从索引读取行。
-
在输出每一行之前,
HAVING
将跳过不匹配该子句的那些行 。(HAVING时对结果集做裁剪,并不能减少扫描行数)
快速查询示例:
SELECT COUNT(*) FROM tbl_name;
SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
SELECT MAX(key_part2) FROM tbl_name WHERE key_part1=constant;
SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,... LIMIT 10;
SELECT ... FROM tbl_name ORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;
假设索引列是数字,MySQL仅使用索引树来解析以下查询:
SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
SELECT COUNT(*) FROM tbl_name
WHERE key_part1=val1 AND key_part2=val2;
SELECT key_part2 FROM tbl_name GROUP BY key_part1;
以下查询使用索引来按排序顺序检索行,而无需单独的排序遍历:
SELECT ... FROM tbl_name
ORDER BY key_part1,key_part2,... ;
SELECT ... FROM tbl_name
ORDER BY key_part1 DESC, key_part2 DESC, ... ;
2. 范围优化
对于 BTREE 和 HASH 索引,当使用=、<=>、IN、IS NULL 或者 IS NOT NULL 操作符时 ,
关键元素与常量值的比较关系对应一个范围条件。
HASH
索引使用 =, <=>, IN(),IS NULL,或IS NOT NULL操作符的等式比较会很快。BTREE
索引使用 >, <, >=, <=, BETWEEN, !=,或 <> ,LIKE ‘pattern’(其中 'pattern’不以通配符开始)操作符,关键元素与常量值的比较关系对应一个范围条件。
范围优化器尝试从WHERE
子句中为每个可能的索引提取范围条件 。在提取过程中,删除了不能用于构建范围条件的条件,合并了产生重叠范围的条件,并删除了产生空范围的条件。优化过程请参考单部分索引的范围访问方法。
2.1 多值比较的等距范围优化
CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES
(1,1), (1,2), (1,3), (1,4), (1,5),
(2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
ANALYZE TABLE t1;
EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;
MySQL可以选择索引扫描以获取所有行,然后根据子句中的f2 > 40
条件WHERE
以产生最终结果集。范围扫描比全索引扫描更有效,但是在这种情况下不能使用,因为没有条件在第一个索引列f1
上 。但是,从MySQL 8.0.13开始,优化器可以使用一种称为“skip scan”的方法,将一次范围扫描分为多次范围扫描,再将多次扫描的结果合并返回。
算法的运行方式如下:
- 获取第一个索引的第一个不同值(
f1 = 1
)。 - 根据第一和第二索引构造范围(
f1 = 1 AND f2 > 40
)。 - 执行范围扫描。
- 获取第一个索引的下一个不同值(
f1 = 2
)。 - 根据第一和第二索引构造范围(
f1 = 2 AND f2 > 40
)。 - 执行范围扫描。
使用此策略可减少访问的行数,因为MySQL会跳过不符合每个构造范围的行。此“skip scan”访问方法适用于以下情况:
- 表T具有至少一个复合索引,其索引的形式为([A_1,…,A_
k
,] B_1,…,B_m
,C [,D_1,…,D_n
])。关键部分A和D可能为空,但B和C必须为非空。。 - 该查询仅使用一个表。
- 查询不使用
GROUP BY
或DISTINCT
。 - 该查询仅引用索引中的列。
- 前缀A_1,…,A_ *
k
*必须是相等谓词,并且它们必须是常量。这包括IN()
。 - 该查询必须是一个联合查询。即,
AND
的OR
条件。 - C上必须有范围条件。
- 允许在D字段上有过滤条件,但是必须和C上的范围条件一起使用。
对于使用了跳跃范围扫描特性的SQL,使用EXPLAIN查看其执行计划,可以看到:
- 在执行计划输出的Extra一栏中有: Using index for skip scan
- 在执行计划输出的possible_keys一栏中会显示可以使用到的索引
2.2 行构造函数表达式的范围优化
优化程序可以将范围扫描访问方法应用于以下形式的查询:
SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));
为了使优化器使用范围扫描,查询必须满足以下条件:
-
仅使用IN()谓词,不使用NOT IN()。
-
在IN()谓词的左侧 ,行构造器仅包含列引用。
-
在IN()谓词的右侧 ,行构造器仅包含运行时常量。
-
在IN()谓词的右侧 ,有多个行构造器。