【MySQL索引与优化篇】数据库优化及性能分析工具使用

数据库优化及性能分析工具使用

文章目录

  • 数据库优化及性能分析工具使用
    • 1. 数据库服务器优化的步骤
    • 2. 查询系统性能参数
    • 3. 定位执行满的SQL:慢查询日志
    • 4. 查看SQL执行成本:show profile
    • 5. 分析查询工具:explain
      • 5.1 id
      • 5.2 type
    • 6. explain进一步使用
      • 6.1 explain支持4种输出格式
      • 6.2 show warnings
    • 7. 分析优化器执行计划:trace
    • 8. MySQL监控分析视图:sys_schema
      • 8.1 sys schema视图摘要
      • 8.2 sys schema使用场景
        • 索引情况
        • 表相关
        • 语句相关
        • IO相关
        • Innodb相关

1. 数据库服务器优化的步骤

  1. 观察服务器是否存在周期性波动,如淘宝双11等周期性活动,此时可考虑加缓存及更改缓存失效策略解决问题
  2. 开启慢查询日志记录,使用SHOW PROFILING和EXPLAIN分析,看是SQL等待时间长还是SQL执行时间长
    • SHOW PROFILING显示等待时间长,则调优服务器参数
    • SHOW PROFILING显示执行时间长,则结合EXPLAIN工具分析,考虑从以下3个方面进行优化
      1. 索引设计优化
      2. JOIN表过多,需要优化
      3. 数据库表设计优化
  3. 考虑SQL查询是否到达瓶颈,未到达则重新检查SQL,或再进行上述分析;已到达则考虑下面两步
    1. 读写分离(主从架构)
    2. 分库分表(垂直分库,垂直分表,水平分表)

数据库调优维度:

  • 索引失效,没有充分利用到索引——索引建立
  • 关联查询太多JOIN(设计缺陷或不得已的需求)——SQL优化
  • 服务器调优及各个参数设置(缓冲、线程数等)——调整my.cnf
  • 数据过多——分库分表

2. 查询系统性能参数

在MySQL中,可以使用 show status 语句查询数据库服务器的 性能参数执行频率

show [global|session] status like '参数';
  • Connections:连接MySQL服务器的次数
  • Uptime:服务器的上线时间,单位秒
  • Slow_queries:慢查询的次数
  • Innodb_rows_read/Innodb_rows_inserted/Innodb_rows_updated/Innodb_rows_deleted:查询对应操作查询或影响的行数
  • Com_select/Com_insert/Com_update/Com_delete:查询对应操作的次数
  • last_query_cost统计SQL的查询成本,返回上一条查询所需要读取的页的数量
    • 执行完 SQL 语句之后,通过查看当前会话中的 last_query_cost 变量值来得到当前查询的成本。它通常也是我们 评价一个查询的执行效率 的一个常用指标。这个查询成本对应的是 SQL 语句所需要读取的页的数量
    • 使用场景:它对于比较开销是非常有用的,特别是我们有好几种查询方式可选的时候

SQL 查询是一个动态的过程,从页加载的角度来看,我们可以得到以下两点结论

  1. 位置决定效率,页存在于内存中,会比在磁盘中读取效率高很多
  2. 批量决定效率,采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多

所以我们首先要考虑数据存放的位置,如果是经常使用的数据就要尽量放到 缓冲池 中,其次我们可以充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就得到了提升。

3. 定位执行满的SQL:慢查询日志

慢查询日志会记录在MySQL中 响应时间超过阀值 的语句,具体指运行时间超过 long_query_time 值的SQL,则会被记录到慢查询日志中,默认值10(秒)

默认情况,MySQL数据库没有开启慢查询日志如果不是调优需要,一般不建议启动该参数(会带来一定的性能影响)

show [global|session] variables like '参数'

永久设置方式:修改my.cnf文件,[mysqld]下增加或修改参数long_query_time、slow_query_log、slow_query_log_file后,重启MySQL

[mysqld]
slow_query_log=ON # 慢查询日志开关
slow_query_log_file=/var/lib/mysql/slow_query_log.log # 慢查询日志位置
long_query_time=3 # 慢查询阈值,单位秒
log_output=FILE # 日志存储方式,log_output='TABLE'表示将日志存入数据库,这样日志信息就会被写入到mysql.slow_log表中

如果不指定存储路径,默认存储到MySQL的数据文件夹下;不指定文件名,默认为hostname-slow.log

慢日志分析工具mysqldumpslow,可以通过该工具按查询时间,平均查询时间等查看慢SQL日志情况

mysqldumpslow --help

删除慢查询日志:手动删除即可,之后执行如下命令重新生成查询日志文件

mysqladmin -uroot -p flush-logs slow

4. 查看SQL执行成本:show profile

Show Profile是MySQL提供的可以用来分析当前会话中SQL 都做了什么、执行的资源消耗情况的工具,可用于sql调优的测量。 默认情况下处于关闭状态 ,并保存最近15次的运行结果。

使用时建议用命令窗口登陆mysql再执行相应命令查看情况,使用界面工具直接执行会有很多干扰

show variables like 'profiling'; -- 查看开关
set profiling = 'ON'; -- 设置开关,ON|OFF
show profiles; -- 查看最近15次运行耗时
show profile; -- 查看上次sql各阶段执行时间,也可看information_schema.profiling表查看上面15次执行情况
show profile for query 82; -- 查看query_id为82的查询时间
show profile cpu,block io for query 1; -- 同时查看cpu和block io情况

【MySQL索引与优化篇】数据库优化及性能分析工具使用_第1张图片

executing:SQL的执行时间,如果该项时间过长,则考虑使用explain工具分析SQL进行SQL优化,否则看其他项考虑数据库参数配置优化

show profile常用参数

  1. all:显示所有的开销信息
  2. block io:显示块IO开销
  3. context switches:上下文切换开销
  4. cpu:显示CPU开销信息
  5. ipc:显示发送和接收开销信息
  6. memory:显示内存开销信息
  7. page faults:显示页面错误开销信息
  8. source:显示和Source_function,Source_file,Source_line相关的开销信息
  9. swaps:显示交换次数开销信息

日常开发需注意:

  1. converting HEAP to MyISAM:查询结果太大,内存不够,数据往磁盘上搬了
  2. Creating tmp table:创建临时表。先拷贝数据到临时表,用完后再删除临时表
  3. Copying to tmp table on disk:把内存中临时表复制到磁盘上,警惕!
  4. locked

如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化

注意: show profile 命令将被弃用,我们可以从 information_schema.profiling 数据表进行查看(会话级别),该表也只会保存最近15条SQL的执行情况

5. 分析查询工具:explain

MySQL提供了 explain 语句供查看SQL语句的具体执行计划,注意:部分统计信息是估算的,并非精确值

explain select 1;
列名 描述
id 每个select对应的唯一的id
select_type select关键字对应的那个查询的类型
table 表名,每有一个表则对应一条数据,可能会有临时表
partitions 匹配的分区信息
type 针对单表的访问方法
possible_keys 可能用到的索引
key 实际上使用的索引
key_len 单位字节,实际使用到的索引长度,主要针对于联合索引有参考意义
ref 当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows 预估的需要读取的记录条数,值越小越好
filtered 某个表经过搜索条件过滤后剩余记录条数的百分比,连接查询中驱动表的执行计划记录的该值决定了被驱动表的执行次数(rows * filtered)
Extra 描述SQL执行计划中的一些额外信息,更准确的描述MySQL到底如何执行给定的语句,主要规避:Using filesort和Using temporary

5.1 id

select对应的唯一id,当有多个表时会有多条记录如:

select * from s1 join s2; -- 根据table情况会有两行数据,但是select语句只有一条,故它们的id都是1

如果SQL语句中写有多个select,但id数无法对上,则表示优化器对sql进行了重写

  • id如果相同,可以认为是一组,从上往下顺序执行
  • 在所有组中,id值越大,优先级越高,越先执行
  • 关注点: 每个id,表示一趟独立的查询,一个sql的查询趟数越少越好

5.2 type

执行计划的一条记录就代表着MySQL对某个表的 执行查询时的访问方法,又称“访问类型”,其中的 type 列就表明了这个访问方法是啥,是较为重要的一个指标。比如,看到type 列的值是 ref,表明MySQL 即将使用 ref 访问方法来执行对 s1 表的查询

完整的访问方法: system,const,eq_ref,ref,fulltext,ref_or_null,index_merge,index_subquery,range,index,ALL

system:当表中只有一条记录,并且该表使用的存储引擎的统计数据是精确的,比如MyISAM和Memory

select count(*) from f1; -- f1使用MyISAM存储引擎,表中会记录记录数,故是system

const:主键或者唯一二级索引列与常数进行等值匹配时,对单表的访问方法就是const

select * from f1 where a = 1; -- a为主键或唯一索引

eq_ref:在连接查询时,如果被驱动表是通过主键或者唯一二级索引列等值匹配的方式进行访问的(如果该主键或者唯一二级索引是联合索引的话,所有的索引列都必须进行等值比较) ,则对该被驱动表的访问方法就是 eq_ref

select * from f1 join f2 on f1.id = f2.id; -- f1需要查全表,而对f2来说,驱动表f1的每条数据都能被被驱动表f2通过主键或唯一索引找到对应数据

ref:当通过普通的二级索引列与常量进行等值匹配时来查询某个表,那么对该表的访问方法就可能是 ref

select * from f1 where b = 'abc'; -- b为普通索引

ref_or_null:当对普通二级索引进行等值匹配查询,该索引列的值也可以是NULL值时,那么对该表的访问方法就可能是ref_or_null

select * from f1 where b = 'abc' or b is null; -- 在ref情况下增加还可能是null的情况

index_merge:单表访问方法时在某些场景下可以使用Union、Intersection、Sort-Union,这三种索引合并的方式来执行查询

select * from f1 where a = 'a' or b = 'b'; -- a和b均为索引,则会将a、b索引都用上

unique_subquery:是针对在一些包含 IN 子查询的查询语句中,如果查询优化器决定将 IN 子查询转化为 exists子查询,而且子查询可以使用到主键进行等值匹配的话,那么该子查询执行计划的 type列就是 unique_subquery

select * from f1 where b in ( select id from f2 where f1.a = f2.a) or c = 'abc'; -- id为f2的主键,类似于eq_ref

range:使用索引获取某些范围区间的记录,那么就可能使用到range访问方法

select * from f1 where b in ('a', 'b', 'c');
select * from f1 where b > 'a' and b < 'c';

index:当可以使用索引覆盖,但需要扫描全部的索引记录时,该表的访问方式就可能是index

select b from f1 where c = 'x'; -- f1上存在联合索引 a,b,c,查询时就不用回表了

小结,结果值从最好到最坏依次是:

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

SQL性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 const 级别 (阿里巴巴开发手册)

6. explain进一步使用

6.1 explain支持4种输出格式

传统、json、tree和可视化界面,其中Json比传统模式多了成本信息的打印,可视化界面需借助MySQL Workbench查看SQL的执行计划

explain format=json select * from f1;

6.2 show warnings

在使用explain后,紧接着使用show warnings语句可以查看与对应查询的查询计划有关的扩展信息,分别是 LevelCodeMessage

如果code值是1003,则表示有对SQL重写,通过show warnings的message可以查看到SQL优化后的执行语句

7. 分析优化器执行计划:trace

OPTIMIZER_TRACE 是MySQL 5.6引入的一项跟踪功能,它可以跟踪优化器做出的各种决策(比如访问表的方法各种开销计算、各种转换等),并将跟踪结果记录到 INFORMATION_SCHEMA.OPTIMIZER_TRACE 表中此功能默认关闭。开启trace,并设置格式为 JSON,同时设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整展示。这里仅做演示,不做详细剖析。

set optimizer_trace="enabled=on",end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
mysql> select * from INFORMATION_SCHEMA.OPTIMIZER_TRACE\G;
*************************** 1. row ***************************
/* 第1部分:SQL语句 */
                            QUERY: select * from niuke.user_profile
/* 第2部分:QUERY字段对应语句的跟踪信息 */
                            TRACE: {
  "steps": [
    {
      /* 预备工作 */
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `niuke`.`user_profile`.`id` AS `id`,`niuke`.`user_profile`.`device_id` AS `device_id`,`niuke`.`user_profile`.`gender` AS `gender`,`niuke`.`user_profile`.`age` AS `age`,`niuke`.`user_profile`.`university` AS `university`,`niuke`.`user_profile`.`gpa` AS `gpa`,`niuke`.`user_profile`.`active_days_within_30` AS `active_days_within_30`,`niuke`.`user_profile`.`question_cnt` AS `question_cnt`,`niuke`.`user_profile`.`answer_cnt` AS `answer_cnt` from `niuke`.`user_profile`"
          }
        ] /* steps */
      } /* join_preparation */
    },
    {
      /* 进行优化 */
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "table_dependencies": [
              {
                "table": "`niuke`.`user_profile`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ] /* depends_on_map_bits */
              }
            ] /* table_dependencies */
          },
          {
            "rows_estimation": [
              {
                "table": "`niuke`.`user_profile`",
                "table_scan": {
                  "rows": 7,
                  "cost": 0.25
                } /* table_scan */
              }
            ] /* rows_estimation */
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ] /* plan_prefix */,
                "table": "`niuke`.`user_profile`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 7,
                      "access_type": "scan",
                      "resulting_rows": 7,
                      "cost": 0.95,
                      "chosen": true
                    }
                  ] /* considered_access_paths */
                } /* best_access_path */,
                "condition_filtering_pct": 100,
                "rows_for_plan": 7,
                "cost_for_plan": 0.95,
                "chosen": true
              }
            ] /* considered_execution_plans */
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": null,
              "attached_conditions_computation": [
              ] /* attached_conditions_computation */,
              "attached_conditions_summary": [
                {
                  "table": "`niuke`.`user_profile`",
                  "attached": null
                }
              ] /* attached_conditions_summary */
            } /* attaching_conditions_to_tables */
          },
          {
            "finalizing_table_conditions": [
            ] /* finalizing_table_conditions */
          },
          {
            "refine_plan": [
              {
                "table": "`niuke`.`user_profile`"
              }
            ] /* refine_plan */
          }
        ] /* steps */
      } /* join_optimization */
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ] /* steps */
      } /* join_execution */
    }
  ] /* steps */
}
/* 第3部分:跟踪信息过长,被截断的部分 */
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
/* 第4部分:执行跟踪语句的用户是否有查看对象的权限。当不具备缺陷的时候,该列值为1,且trace字段为空,一般在调用带有存储过程的情况下会出现这种情况 */
          INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

8. MySQL监控分析视图:sys_schema

关于MySQL的性能监控和问题诊断,我们一般都从 performance_schema 中去获取想要的数据,在MySQL5.7.7版本中新增 sys_schema,它将performance_schemainformation_schema中的数据以更容易理解的方式总结归纳为“视图”,其目的就是为了降低查询performance_schema的复杂度,让DBA能够快速的定位问题。都有哪些监控表和视图,掌握了这些,在我们开发和运维的过程中就起到了事半功倍的效果

8.1 sys schema视图摘要

  1. 主机相关:以host_summary开头,主要汇总了IO延迟的信息
  2. Innodb相关: 以innodb开头,汇总了innodb buffer信息和事务等待innodb锁的信息
  3. I/O相关: 以io开头,汇总了等待I/O、I/O使用量情况
  4. 内存使用情况:以memory开头,从主机、线程、事件等角度展示内存的使用情况
  5. 连接与会话信息:processlist和session相关视图,总结了会话相关信息
  6. 表相关: 以schema_table开头的视图,展示了表的统计信息
  7. 索引信息:统计了索引的使用情况,包含冗余索引和未使用的索引情况
  8. 语句相关:以statement开头,包含执行全表扫描、使用临时表、排序等的语句信息
  9. 用户相关:以user开头的视图,统计了用户使用的文件I/O、执行语句统计信息
  10. 等待事件相关信息:以wait开头,展示等待事件的延迟情况

8.2 sys schema使用场景

索引情况
# 1. 查询冗余索引
select * from sys.schema_redundant_indexes;
# 2. 查询未使用过的索引
select * from sys.schema_unused_indexes;
# 3. 查询索引的使用情况
select * from sys.schema_index_statistics where table_schema = 'dbname';
表相关
# 1. 查询表的访问量
select table_schema, table_name, sum(io_read_requests + io_write_requests) as io from sys.schema_table_statistics group by table_schema, table_name order by io desc;
# 2. 查询占用buffer pool较多的表
select object_schema,object_name,allocated,data from sys.innodb_buffer_stats_by_table order by allocated limit 10;
# 3. 查询表的全表扫描情况
select * from sys.statements_with_full_table_scans where db='dbname';
语句相关
# 1. 监控SQL执行的频率
select db,exec_count,query from sys.statement_analysis order by exec_count desc;
# 2. 监控使用了排序的SQL
select db,exec_count,first_seen,last_seen,query from sys.statements_with_sorting limit 1;
# 3. 监控使用了临时表或者磁盘临时表的SQL
select db,exec_count,tmp_tables,tmp_disk_tables,query from sys.statement_analysis where tmp_tables > 0 or tmp_disk_tables > 0 order by (tmp_tables + tmp_disk_tables) desc;
IO相关
# 1. 查看磁盘消耗IO文件
select file,avg_read,avg_write,avg_read + avg_write as avg_io from sys.io_global_by_file_by_bytes order by avg_read limit 10;
Innodb相关
# 1. 行锁阻塞情况
select * from sys.innodb_lock_waits;

风险提示:通过sys库去查询时,MySQL会 消耗大量资源 去收集相关信息,严重的可能会导致业务请求被阻塞,从而引起故障。建议生产上 不要频繁 的去查询sys或者performance_schema、information_schema来完成监控、巡检等工作

你可能感兴趣的:(MySQL,数据库,mysql)