性能分析
性能分析
索引优化
常用分析
SQL 执行频率
show [global | session] status like 'Com_______'
可以查出来 sql 的执行频率,来确认数据库中哪个用的最多
慢查询日志
可以定位执行效率低的 SQL
# 开启慢查询日志功能
SET GLOBAL slow_query_log = 'ON';
# 慢查询日志存放位置
SET GLOBAL slow_query_log_file = '/var/lib/mysql/ranking-list-slow.log';
# 无论是否超时,未被索引的记录也会记录下来。
SET GLOBAL log_queries_not_using_indexes = 'ON';
# 慢查询阈值(秒),SQL 执行超过这个阈值将被记录在日志中。
SET SESSION long_query_time = 1;
# 慢查询仅记录扫描行数大于此参数的 SQL
SET SESSION min_examined_row_limit = 100;
这里对日志中的信息如下:
- Time :被日志记录的代码在服务器上的运行时间。
- User@Host:谁执行的这段代码。
- Query_time:这段代码运行时长。
- Lock_time:执行这段代码时,锁定了多久。
- Rows_sent:慢查询返回的记录。
- Rows_examined:慢查询扫描过的行数。
profile 详情
可以看到所有 sql 时间耗费在哪
现在已经弃用了,未来可能删除,用 performance schema 替代
select @@have_profiling # 查看当前 MySQL是否支持profiling
select @@profiling
set @@profiling = 1 # set profiling = 1
show profiles;
可以展示当前 Session 下所有 SQL 语句的简要的信息包括 Query_ID(SQL 语句的 ID 编号) 和 Duration(耗时)。SHOW PROFILE FOR QUERY {Query_ID}
分析一条语句耗时细节,具体语法如下:
SHOW PROFILE [type [, type] ... ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type: {
ALL
| BLOCK IO
| CONTEXT SWITCHES
| CPU
| IPC
| MEMORY
| PAGE FAULTS
| SOURCE
| SWAPS
}
explain 执行计划
执行计划 是指一条 SQL 语句在经过 MySQL 查询优化器 优化会后,具体的执行方式。
执行计划通常用于 SQL 性能分析、优化等场景。通过 EXPLAIN
的结果,可以了解到如数据表的查询顺序、数据查询操作的操作类型、哪些索引可以被命中、哪些索引实际会命中、每个数据表有多少行记录被查询等信息。具体字段介绍如下:
type(重要)
查询执行的类型,描述了查询是如何执行的。所有值的顺序从最优到最差排序为:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
常见的几种类型具体含义如下:
- system:如果表使用的引擎对于表行数统计是精确的(如:MyISAM),且表中只有一行记录的情况下,访问方法是 system ,是 const 的一种特例。
- const:表中最多只有一行匹配的记录,一次查询就可以找到,常用于使用主键或唯一索引的所有字段作为查询条件。
- eq_ref:唯一索引扫描,当连表查询时,前一张表的行在当前这张表中只有一行与之对应。是除了 system 与 const 之外最好的 join 方式,常用于使用主键或唯一索引的所有字段作为连表条件。
- ref:非唯一索引扫描,使用普通索引作为查询条件,查询结果可能找到多个符合条件的行。
- index_merge:当查询条件使用了多个索引时,表示开启了 Index Merge 优化,此时执行计划中的 key 列列出了使用到的索引。
- range:对索引列进行范围查询,执行计划中的 key 列表示哪个索引被使用了。
- index:全索引扫描查询遍历了整棵索引树,索引一般在内存中,速度更快。
- ALL:全表扫描。
key(重要)
key 列表示 MySQL 实际使用到的索引。如果为 NULL,则表示未用到索引。
Extra(重要)
这列包含了 MySQL 解析查询的额外信息,通过这些信息,可以更准确的理解 MySQL 到底是如何执行查询的。常见的值如下:
- Using filesort:在排序时使用了外部的索引排序,没有用到表内索引进行排序。
- Using temporary:MySQL 需要创建临时表来存储查询的结果,常见于 ORDER BY 和 GROUP BY。
- Using index:表明查询使用了覆盖索引,不用回表,查询效率非常高。
- Using index condition:表示查询优化器选择使用了索引条件下推这个特性。
- Using where:表明查询使用了 WHERE 子句进行条件过滤。一般在没有使用到索引的时候会出现。
- Using join buffer (Block Nested Loop):连表查询的方式,表示当被驱动表的没有使用索引的时候,MySQL 会先将驱动表读出来放到 join buffer 中,再遍历被驱动表与驱动表进行查询。
这里提醒下,当 Extra 列包含 Using filesort 或 Using temporary 时,MySQL 的性能可能会存在问题,需要尽可能避免。
性能优化方法
避免使用 SELECT *
SELECT * 会消耗更多的 CPU。
SELECT * 无用字段增加网络带宽资源消耗,增加数据传输时间,尤其是大字段(如 varchar、blob、text)。
SELECT * 无法使用 MySQL 优化器覆盖索引的优化
基于 MySQL 优化器的“覆盖索引”策略效率极高,业界极为推荐的查询优化方式
SELECT <字段列表> 可减少表结构变更带来的影响。
分页优化
普通的分页在数据量小的时候耗费时间还是比较短的。如果数据量变大,达到百万甚至是千万级别,普通的分页耗费的时间就非常长了。
优化超多分页场景可以利用:
- 延迟关联
- 子查询
子查询:
SELECT `score`,`name` FROM `cus_order` WHERE id >= (SELECT id FROM `cus_order` LIMIT 1000000, 1) LIMIT 10
我们先查询出 limit 第一个参数对应的主键值,再根据这个主键值再去过滤并 limit,这样效率会更快。
不过,子查询的结果会产生一张新表,会影响性能,应该尽量避免大量使用子查询。
延迟查询:
SELECT `score`,`name` FROM `cus_order` a, (SELECT id from `cus_order` ORDER BY `score` DESC LIMIT 1000000, 10) b where a.id = b.id
尽量避免多表做 join
阿里巴巴《Java 开发手册》中有这样一段描述:
【强制】超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致;多表关联查询时,保证被关联 的字段需要有索引。
join 的效率比较低,主要原因是因为其使用嵌套循环(Nested Loop)来实现关联查询,三种不同的实现效率都不是很高:
- Simple Nested-Loop Join :没有进过优化,直接使用笛卡尔积实现 join,逐行遍历/全表扫描,效率最低。
- Block Nested-Loop Join :利用 JOIN BUFFER 进行优化,性能受到 JOIN BUFFER 大小的影响,相比于 Simple Nested-Loop Join 性能有所提升。不过,如果两个表的数据过大的话,无论如何优化,Block Nested-Loop Join 对性能的提升都非常有限。
- Index Nested-Loop Join :在必要的字段上增加索引,使 join 的过程中可以使用到这个索引,这样可以让 Block Nested-Loop Join 转换为 Index Nested-Loop Join,性能得到进一步提升。
实际业务场景避免多表 join 常见的做法有两种:
- 单表查询后在内存中自己做关联 :对数据库做单表查询,再根据查询结果进行二次查询,以此类推,最后再进行关联。
- 数据冗余,把一些重要的数据在表中做冗余,尽可能地避免关联查询。很笨的一种做法,表结构比较稳定的情况下才会考虑这种做法。进行冗余设计之前,思考一下自己的表结构设计的是否有问题。
批量操作
对于数据库中的数据更新,如果能使用批量操作就要尽量使用,减少请求数据库的次数,提高性能。
# 反例
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 426547, 'user1');
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 33, 'user2');
INSERT INTO `cus_order` (`id`, `score`, `name`) VALUES (1, 293854, 'user3');
# 正例
INSERT into `cus_order` (`id`, `score`, `name`) values(1, 426547, 'user1'),(1, 33, 'user2'),(1, 293854, 'user3');
尽量用 UNION ALL 代替 UNION
UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作,更耗时,更消耗 CPU 资源。
UNION ALL 不会再对结果集进行去重操作,获取到的数据包含重复的项。
不过,如果实际业务场景中不允许产生重复数据的话,还是可以使用 UNION。
正确使用索引
选择合适的字段创建索引
- 不为 NULL 的字段 :索引字段的数据应该尽量不为 NULL,因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL,建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
- 被频繁查询的字段 :我们创建索引的字段应该是查询操作非常频繁的字段。
- 被作为条件查询的字段 :被作为 WHERE 条件查询的字段,应该被考虑建立索引。
- 频繁需要排序的字段 :索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
- 被经常频繁用于连接的字段 :经常用于连接的字段可能是一些外键列,对于外键列并不一定要建立外键,只是说该列涉及到表与表的关系。对于频繁被连接查询的字段,可以考虑建立索引,提高多表连接查询的效率。
被频繁更新的字段应该慎重建立索引
虽然索引能带来查询上的效率,但是维护索引的成本也是不小的。 如果一个字段不被经常查询,反而被经常修改,那么就更不应该在这种字段上建立索引了。
尽可能的考虑建立联合索引而不是单列索引
因为索引是需要占用磁盘空间的,可以简单理解为每个索引都对应着一颗 B+树。如果一个表的字段过多,索引过多,那么当这个表的数据达到一个体量后,索引占用的空间也是很多的,且修改索引时,耗费的时间也是较多的。如果是联合索引,多个字段在一个索引上,那么将会节约很大磁盘空间,且修改数据的操作效率也会提升。
注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
考虑在字符串类型的字段上使用前缀索引代替普通索引
前缀索引仅限于字符串类型,较普通索引会占用更小的空间,所以可以考虑使用前缀索引带替普通索引。
避免索引失效
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
删除长期未使用的索引
删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用