mysql语句级查询优化之二

mysql语句级查询优化之一
mysql语句级查询优化之二

优化 SQL 的最高境界是回归需求,只有正确的理解了需求,才能写出高效的SQL,因此改写 SQL 往往是优化SQL 的重要手段。
虽然说读懂执行计划是SQL 优化中极其重要的环节,但是如果不能读懂需求,写出复杂繁琐绕了九曲十八弯的SQL 语句,通过分析执行计划来调优,往往还是会陷入到束手无策之中。

数据库中表和数据一一Oracle数据库scott数据库

1 mysql索引综述

1.1 索引类型与存储引擎的关系

1)聚簇索引中叶子节点存储的是主键值以及对应数据行本身。
2)非聚簇索引中叶子节点存储的主键值和对应数据行的引用地址。
1)innodb存储引擎 可以创建【聚簇索引】和【非聚簇索引】。
2)myisam存储引擎 只能创建【非聚簇索引】。

1.2 索引算法

索引算法决定索引数据存储方式和查询数据方式。常用算法有 B+Tree 算法,Hash 算法,全文索引算法。

1.2.1 二分查找算法

二分查找法的基本思想是:将记录排序(假如从小到大排序),然后采用跳跃式的方式进行查找,以有序数列的中间位置为比较对象;如果要找的元素小于该中点元素,那么查找左半部分;如果要找的元素大于该中点元素,那么就查找右半部分。

1.2.2 平衡二叉树算法

平衡二叉树算法:是二分查找算法的升级版;平衡二叉树,左子树的值总是小于根的值,右子树的值总是大于根的键值,因此可以通过中序遍历(以递归的方式按照左中右的顺序来访问子树)。

1.2.3 B+Tree算法

在 MySQL 中,将相近的数据保存在同一个存储块中。查询根据 KEY 找到存储块,然后再从存储块中找到对应的数据或则是数据行地址进行定位。
因此这里涉及到一个概念。就是磁盘的 I/O,也就是读取存储块的次数。这个次数越少,那么查询的速度就越快。
那么如何减少这个次数呢? 最简单的方式就是让存储块存储的内容增加,这样就可以减少磁盘 I/O 读取次数,从而提高查询速度。而 B+Tree 算法就是为了这个目的而诞生的。

1.2.4 hash算法

哈希索引 (hash index) 基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码 (hash code),哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样.哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。

2 Explain执行计划

通过 explain 查看SQL执行的效率。通过 explain 可以查看如下信息:
1)查看表的加载顺序。
2)查看 sql 的查询类型。
3)哪些索引可能被使用,哪些索引实际使用了。
4)表之间的引用关系。
5)一个表中有多少行被优化器查询。
6)其他一些额外的辅助信息。

2.1 执行计划之 id 属性

ld 属性是 mysgl 对查询语句中提供查询序号。用于表示本次查询过程中加载表的顺序或则查询子句执行顺序
ld 届性有二种情况
id 相同表示加载表的顺序是从上到下
id 不同 id 值越大,优先级越高,越先被执行id 有相同,也有不同,同时存在。id 相同的可以认为是一组,从上往下顺序执行:在所有的组中,id 的值越大,优先级越高,越先执行。

2.1.1 id 的属性相同表示加载表的顺序是从上到下

EXPLAIN 
SELECT DNAME, ENAME 
FROM DEPT LEFT JOIN EMP ON DEPT.DEPTNO = EMP.DEPTNO

2.1.2 id 值越大,优先级越高

EXPLAIN 
SELECT ENAME, JOB, SAL 
FROM EMP 
WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT)

2.1.3 id 有相同,也有不同,同时存在

EXPLAIN 
SELECT ENAME,DNAME 
FROM EMP 
JOIN (SELECT DEPTNO,DNAME FROM DEPT GROUP BY DEPTNO,DNAME) E ON EMP.DEPTNO = E.DEPTNO

2.2 select_type

查询类型,主要用于区分普通查询、联合查询、子查询等。

2.2.1 Simple

表示当前查询语句是一个简单查询语句。不包含子查询,不包含联合查询,不包含连接查询

EXPLAIN 
SELECT ENAME, JOB, SAL
FROM EMP

2.2.2 Primary

如果执行的是一个包含子查询的查询,或则是一个联合查询。Primary 指向的外部查询语句或则是联合查询中的第一个子查询语句

EXPLAIN
SELECT EMPNO,ENAME FROM EMP 
UNION 
SELECT DEPTNO,DNAME FROM DEPT

2.2.3 DEPENDENT SUBQUERY

表示当前查询语句是一个子查询。并且执行条件依赖与外部查询提供的条件.

EXPLAIN 
SELECT E1.ENAME, E1.JOB, E1.SAL, (SELECT MAX(E2.SAL) FROM EMP E2 WHERE E2.JOB=E1.JOB)
FROM EMP E1

2.2.4 SUBQUERY

表示当前查询是一个子查询。并且这个子查询在执行时不害要得到外部查询的帮助

EXPLAIN 
SELECT ENAME, JOB, SAL, (SELECT COUNT(*) FROM DEPT) CNT
FROM EMP

2.3 possible keys

表示当前查询语句执行时可能用到的索引有哪些,在possible keys 可能出现多个索引,但是这些索引未必在本次查询使用到。

2.4 key

表示当前查询语句真实使用的索引名称。如果这个字段为 null,则有两中可能,一个是当前表中没有索引。二是当前表有索引但是失效了。

2.5 key_len

如果本次查询使用了索引。则 key_len 内容不为空
表示当前索引字段存储内容最大长度。这个长度不是精准值。只是 mysql 估计的值。这个值越大越精准。在能得到相同结果时,这个值越小那么查询速度越快。

2.6 type

Type 属性描述 mysql 对本次查询的评价,是执行计划中的一个重要属性。查询语句执行效率从高到底的顺序依次是:
NUILL:无需访问表或者索引,比如获取一个索引列的最大值或最小值。
system / const:当查询最多匹配一行时,常出现于 where 条件是 = 的情况。systemconst 的一种特殊情况,既表本身只有一行数据的情况。
eq_ref:关联查询时,根据唯一非空索引进行查询的情况。
ref:查询时,根据非唯一非空索引进行查询的情况。
range:在一个索引上进行范围查找。
index:遍历索引树查询,通常发生在查询结果只包含索引字段时。
ALL:全表扫描,没有任何索引可以使用时。这是最差的情况,应该避免。

从好到坏排序。一般要求至少是range 级别,最好能达到ref 级别。

system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > 
unique_subquery > index_subquery > range > index > ALL

2.7 table

查询的哪个表。

2.8 rows

需要扫描的行数。

4 索引失效的八种情形

4.1 组合索引最左原则

在复合索引查询中,如果不是按照索引的最左列开始查找,则无法使用索引。

4.2 最左前缀模糊查询

like的模糊查询以 % 开头,索引失效。只有 % 不在第一个位置索引才会起作用。

SELECT * FROM t_student WHERE name LIKE '%太白';

4.3 数据类型不一致

如数据库表字段类型为varchar,where条件用number,索引会失效。

SELECT * FROM t_student WHERE idcard = 410181200009065029;
# 数据库中idcard为varchar类型导致索引失效。

4.4 使用函数

对索引的字段使用 函数与计算,索引也会失效。此情况下应该建立基于函数的索引。

SELECT * FROM t_student 
WHERE DATE_FORMAT(create_time,'%Y-%m-%d') = '2022-06-02';
# create_time字段设置索引,那就无法使用函数,否则索引失效。

4.5 为null的查询

索引不存储null值,如果不限制索引列是not null,数据库会认为索引列有可能存在空值,所以不会按照索引进行计算。比如:

# 不走索引。
SELECT * FROM t_student WHERE address IS NULL;
# 走索引。
SELECT * FROM t_student WHERE address IS NOT NULL;

建议大家这设计字段的时候,如果没有必要的要求必须为NULL,那么最好给个默认值空字符串,这可以解决很多后续的麻烦(切记)。

4.6 使用算术运算

对索引列进行(+,-,*,/,!, !=, <>)等运算,会导致索引失效。

SELECT * FROM user WHERE age - 1 = 20;

4.7 全表扫描更快时

如果数据库预计使用全表扫描要比使用索引快,则不使用索引。

4.8 尽量避免 OR 操作

如果条件中有 or 则要求使用的所有字段都必须建立索引,否则不使用索引。

SELECT * FROM users WHERE username='michael' or userage=20 or usersex='male';

除非每个列都建立了索引,否则不建议使用OR,在多列OR 中,可以考虑用UNION 替换。

SELECT * FROM users WHERE username='michael' UNION
SELECT * FROM users WHERE userage=20 UNION
SELECT * FROM users WHERE usersex='male'

5 查询优化示例

5.1 分解关联查询

将一个大的查询分解为多个小查询是很有必要的。
很多高性能的应用都会对关联查询进行分解,就是可以对每一个表进行一次单表查询,
然后将查询结果在应用程序中进行关联,很多场景下这样会更高效。

SELECT * 
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN item i ON o.id = i.order_id
WHERE u.userid= 200;
分解为:
SELECT * FROM users WHERE id = 200;
SELECT * FROM order WHERE id = 200;
SELECT * FROM item WHERE id in (12321,32412,32321);

5.2 优化LIMIT 分页

在系统中需要分页的操作通常会使用limit 加上偏移量的方法实现,同时加上合适的order by 子句。如果有对应的索引,通常效率会不错。
如果当偏移量非常大的时候,例如可能是limit 1000000,20 这样的查询,这是mysql 需要查询1000020 条然后只返回最后20 条,前面的1000000 条记录都将被舍弃,这样的代价很高。
使用子查询优化大数据量分页查询

select * from users where username = 'michael' limit 1000000,100;

select * 
from users 
where username = 'michael' and 
	  id >= (select id 
	  		 from users 
	  		 where username = 'michael' LIMIT 1000000,1) LIMIT 100;

使用id 限定优化大数据量分页查询
使用这种方式需要先假设数据表的id 是连续递增的,我们根据查询的页数和查询的记
录数可以算出查询的id 的范围,可以使用id between and 来查询。

select * 
from users 
where username = 'michael' and 
	 (userid between 1000001 and 1000100)
LIMIT 100;

猜你喜欢

转载自blog.csdn.net/Michael_lcf/article/details/106992798