查询性能优化
ch6 查询性能优化
慢查询基础:优化数据访问
优化小细节
- 当使用索引列进行查询的时候尽量不要使用表达式,把计算放到业务层而不是数据库层
- 尽量使用主键查询,而不是其他索引,因此主键查询不会触发回表查询
- 使用前缀索引
- 使用索引扫描来排序
- union all, in, or都能够使用索引,但是推荐使用in
- 范围列可以用到索引 范围条件是:<、<=、>、>=、between
- 范围列可以用到索引,但是范围列后面的列无法用到索引,索引最多用于一个范围列
- 强制类型转换会全表扫描
- explain select * from user where phone=13800001234; 不会触发索引
- explain select * from user where phone=’13800001234’; 触发索引
- 更新十分频繁,数据区分度不高的字段上不宜建立索引
- 更新会变更B+树,更新频繁的字段建议索引会大大降低数据库性能
- 类似于性别这类区分不大的属性,建立索引是没有意义的,不能有效的过滤数据,
- 一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算
- 创建索引的列,不允许为null,可能会得到不符合预期的结果
- 当需要进行表连接的时候,最好不要超过三张表,因为需要join的字段,数据类型必须一致
- 能使用limit的时候尽量使用limit
- 单表索引建议控制在5个以内
- 单索引字段数不允许超过5个(组合索引)
- 创建索引的时候应该避免以下错误概念
- 索引越多越好
- 过早优化,在不了解系统的情况下进行优化
索引监控
1
show status like 'Handler_read%';
参数解释
- Handler_read_first:读取索引第一个条目的次数 Handler_read_key:通过index获取数据的次数
- Handler_read_last:读取索引最后一个条目的次数 Handler_read_next:通过索引读取下一条数据的次数
- Handler_read_prev:通过索引读取上一条数据的次数 Handler_read_rnd:从固定位置读取数据的次数
- Handler_read_rnd_next:从数据节点读取下一条数据的次数
查询优化器的提示(hint)
如果对优化器选择的执行计划不满意,可以使用优化器提供的几个提示(hint)来控制最终的执行计划。
在查询中加入相应的提示,就可以控制该查询的执行计划。
MySQL :: MySQL 8.0 Reference Manual :: 8.9.3 Optimizer Hints
优化特定类型的查询
优化 COUNT() 查询
- 如果希望知道的是结果集的行数,最好使用
COUNT(*)
,这样写意义清晰,性能也会很好。 - 对于 MyISAM 来说,只有没有任何 WHERE 条件的 COUNT(*) 才非常快。可以利用这个特性来优化查询
- 使用近似值,业务场景不要求完全精确 COUNT 值,可以用 EXPLAIN 出来的优化器估估算的行数就是一个不错的近似值,执行 EXPLAIN 并不需要真地区执行查询,所以成本很低。
- 更复杂优化,索引覆盖扫描,还不够就训词增加汇总表或者外部缓存系统。
或者增加类似 Memcached 这样的外部缓存系统。可能很快你就会发现陷入到一个熟悉的困境,“快速,精确和实现简单”,三者永远只能满足其二,必须舍掉其中一个。
优化关联查询
- 确保 ON 或者 USING 子句的列上有索引
- 确保任何的 GROUP BY 和 ORDER BY 中的表达式只涉及到一个表中的列,这样 MySQL 才有可能使用索引来优化这个过程。
优化子查询
这里的建议:尽可能使用关联查询代替。
优化 GROUP BY 和 DISTINCT
有时候优化器会内部处理时相互转换这两类查询。
在 MySQL 中,当无法使用索引的适合,GROUP BY 使用两种策略来完成:
- 使用临时表
- 文件排序来做分组
对关联查询做分组,并且是按照查找表中的某个列进行分组,那么通常采用查找表的标识列分组的效率会比其他列更高。
优化 GROUP BY WITH ROLLUP
分组查询的一个变种就是要求 MySQL 对返回的分组结果再做一次超级聚合。就可以用 WITH ROLLUP 子句来实现这种逻辑。
优化 LIMIT 分页
优化办法:尽可能地使用索引覆盖扫描,而不是查询所有的列。然后根据需要做一次关联操作返回所需的列。
优化 UNION 查询
优化办法,手动的将 WHERE、LIMIT、ORDER BY 等子句“下推”到 UNION 的各个子查询中,优化器就能充分利用这些条件进行优化。
静态查询分析
Percona Toolkit’s pt-query-advisor 能够解析查询日志,分析查询模式,然后给出所有可能存在的问题的查询,并给出足够详细的建议。相当于给 MySQL 所有的查询做一次全面健康的检查。
使用用户自定义变量
MySQL :: MySQL 8.0 Reference Manual :: 9.4 User-Defined Variables
用户自定义变量是一个用来存储内容的临时容器,在连接 MySQL 的整个过程中都存在。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
SET @var_name = expr [, @var_name = expr] ...
mysql> SET @v1 = X'41';
mysql> SET @v2 = X'41'+0;
mysql> SET @v3 = CAST(X'41' AS UNSIGNED);
mysql> SELECT @v1, @v2, @v3;
+------+------+------+
| @v1 | @v2 | @v3 |
+------+------+------+
| A | 65 | 65 |
+------+------+------+
mysql> SET @v1 = b'1000001';
mysql> SET @v2 = b'1000001'+0;
mysql> SET @v3 = CAST(b'1000001' AS UNSIGNED);
mysql> SELECT @v1, @v2, @v3;
+------+------+------+
| @v1 | @v2 | @v3 |
+------+------+------+
| A | 65 | 65 |
+------+------+------+
- 查询运行时计算总数和平均值
- 模拟 GROUP 语句中的函数 FIRST() 和 LAST()
- 对大量数据做一些数据计算
- 计算一个大表的 MD5 散列值
- 编写一个样本处理函数,当样本中的数值超过某个边界值的适合将其变成0
- 模拟读/写游标
- 在 SHOW 语句的 WHERE 子句中加入变量值