后端开发离不开与数据库打交道, 数据库种类也很多,接触过的有MySQL、Oracle、HBase、Redis等,此文中优化的主要针对MySQL,并结合开发中经常遇到的场景,参考网上资料和相关书籍,将优化记录总结。
下图是根据现阶段理解整理的脑图,后期也会在此基础上补充或修改。
由于篇幅较长,故分开记录,此文主要详细说明MySQL优化之定位问题
。
定位问题
通过show status 命令了解各种SQL的执行频率
SHOW STATUS LIKE 'Com_%';
执行结果如下:
Com_xxx 表示每个xxx语句执行的次数,我们通常比较关心的是一下几个统计参数。
- Com_select:执行SELECT操作的次数,一次查询只累加1;
- Com_insert:执行INSERT操作的次数,对于批量插入的INSERT操作,只累加一次;
- Com_update:执行UPDATE操作的次数;
- Com_delete:执行DELETE操作的次数;
此外,以下几个参数便于用户了解数据库的基本情况: - Connection:试图连接MySQL服务器的次数;
- Uptime:服务器工作时间;
- Slow_queries:慢查询的次数。
定位执行效率低的SQL语句
可以通过以下两种方式定位执行效率低得SQL语句:
- 通过慢查询日志定位那些查询效率较低的SQL语句,使用–slow_query_log[={0|1}]显式指定慢查询的状态,如果不指定值或者指定值为1都会打开慢查询;使用slow_query_log_file[=file_name]来指定慢查询日志的路径;
- 慢查询日志在查询结束后才记录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看SQL的执行情况,同时对一些锁表操作进行优化。
通过EXPLAIN分析低效SQL的执行计划
官方文档:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
通过以上步骤查询到效率低的SQL语句后,可以通过EXPLAIN或者DESC命令获取MySQL如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序。
对每个列简单进行说明:
- select_type:表示SELECT的类型
- SIMPLE:简单的查询,没有子查询和UNION
- PRIMARY: 如果有复杂查询的话,标记最外层的查询为primary
- UNION:UNION中的第二个或者后面的查询语句
- DEPENDENT UNION:UNION中的第二个或后面的SELECT语句,取决于外面的查询
- UNION RESULT:UNION的结果
- SUBQUERY:子查询中的第一个SELECT
- DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询
- DERIVED:派生的表
- MATERIALIZED:实例化的子查询(Materialized subquery)
- UNCACHEABLE SUBQUERY :子查询的结果不能被缓存,必须重新评估为每一行的外部查询
- UNCACHEABLE UNION:UNION中的第二个或后面的SELECT语句,而且不能被缓存。
- table:输出结果集的表
<union*M*,*N*>
,该行引用了id值为M和N的行的联合结果<derived*N*>
,该行引用了id值为N的行的派生表结果,比如引用了从子查询中生成的派生表<subquery*N*>
,该行引用了id值为N的行的实例化子查询结果
- type:表示MySQL在表中找到所需行的方式,或者叫访问类型,常见类型(按照最好到最坏的顺序):
- type=system,表里面只有一条数据,是const的一种特殊情况;
- type=const,单表中最多有一个匹配行,在查询开始时就会被读取。例如,根据主键primary key或者唯一索引unique index进行查询;
- type=eq_ref,类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单的说,就是多表连接中使用primary key或者unique index作为关联条件;
- type=ref,使用非唯一索引的前缀扫描,返回匹配某个单独值得记录行;
- type=fulltext,使用全文索引执行join;
- type=ref_or_null,类似于ref,会额外搜索包含NULL的行,常用于解析子查询;
- type=index_merge,使用了索引合并优化,key列显示了一系列使用到的索引,key_len显示了使用到的索引的最长键长;
- type=unique_subquery,只是一个索引查找函数,它完全替代了子查询以提高效率;
- type=index_subquery,和unique_subquery类似,它在子查询中替换,但在以下形式的子查询中适用于非唯一索引;
- type=range,检索给定范围的行,并使用index来选择行,例如 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, or IN();
- type=index,这个join类型和ALL相似,区别是扫描了索引数。以下两种情况:①当索引是一个覆盖索引,即包含(或覆盖)所有需要查询的字段的值,那么就只需要扫描索引树而无需回表,这个时候Extra列的值就会为Using index,index类型通常要比ALL类型要快,因为索引的长度通常要比表数据小得多。②按索引顺序查找数据行来执行全表扫描,这个时候Extra列的值不会为Using index;
- type=ALL,全表扫描,实际使用中,如果数据量比较大,应该避免进行全表扫描,因为这种连接是最慢的
- possible_keys:表示查询时可能使用的索引
- partitions:查询语句将从其中匹配记录的分区。对于非分区表,值为null
- key:表示实际使用的索引。
- key-len:使用到索引字段的长度。
- rows:扫描行的数量。
- filtered:依据表条件过滤行占比,最大值为100,这意味着没有对行进行筛选。
- Extra:执行情况的说明和描述,包括不适合在其他列中显示但是对执行计划非常重要的额外信息。
有的时候,仅仅通过explain分析执行计划并不能很快的定位SQL的问题,这个时候我们还可以选择profile联合分析。
通过show profile分析SQL
MySQL从5.0.37版本开始增加了对show profiles和show profile语句的支持。通过have_profiling参数能看出当前MySQL是否支持profile:
根据书中(《深入浅出MySQL》)实践,并未出现理想效果:
直接使用Navicat Premium 12
可视化工具就有profile信息了:
通过trace分析优化器如何选择执行计划
MySQL5.6提供了对SQL的跟踪trace,通过trace文件能够进一步了解优化器的行为。
使用方式:首先打开trace,设置格式为JSON,设置trace最大能够使用的内存大小,便面解析过程中因为默认内存过小而不能够完整显示。
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;
确定问题并采取相应的优化措施
经过以上步骤,基本确认出现的原因。
可以通过建立索引,修改字段属性,修改查询语句等方式进行优化,后面文章中将会一一讲解。