数据库调优维度:
在MySQL中,可以使用 show status
语句查询数据库服务器的 性能参数
、执行频率
show [global|session] status like '参数';
last_query_cost
变量值来得到当前查询的成本。它通常也是我们 评价一个查询的执行效率
的一个常用指标。这个查询成本对应的是 SQL 语句所需要读取的页的数量
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
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情况
executing:SQL的执行时间,如果该项时间过长,则考虑使用explain工具分析SQL进行SQL优化,否则看其他项考虑数据库参数配置优化
show profile常用参数:
日常开发需注意:
如果在show profile诊断结果中出现了以上4条结果中的任何一条,则sql语句需要优化
注意: show profile 命令将被弃用,我们可以从 information_schema.profiling 数据表进行查看(会话级别),该表也只会保存最近15条SQL的执行情况
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 |
select对应的唯一id,当有多个表时会有多条记录如:
select * from s1 join s2; -- 根据table情况会有两行数据,但是select语句只有一条,故它们的id都是1
如果SQL语句中写有多个select,但id数无法对上,则表示优化器对sql进行了重写
执行计划的一条记录就代表着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
级别 (阿里巴巴开发手册)
传统、json、tree和可视化界面,其中Json比传统模式多了成本信息的打印,可视化界面需借助MySQL Workbench查看SQL的执行计划
explain format=json select * from f1;
在使用explain后,紧接着使用show warnings语句可以查看与对应查询的查询计划有关的扩展信息,分别是 Level
、Code
、Message
;
如果code
值是1003,则表示有对SQL重写,通过show warnings的message
可以查看到SQL优化后的执行语句
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)
关于MySQL的性能监控和问题诊断,我们一般都从 performance_schema
中去获取想要的数据,在MySQL5.7.7版本中新增 sys_schema
,它将performance_schema
和information_schema
中的数据以更容易理解的方式总结归纳为“视图”,其目的就是为了降低查询performance_schema的复杂度,让DBA能够快速的定位问题。都有哪些监控表和视图,掌握了这些,在我们开发和运维的过程中就起到了事半功倍的效果
# 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;
# 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;
# 1. 行锁阻塞情况
select * from sys.innodb_lock_waits;
风险提示:通过sys库去查询时,MySQL会
消耗大量资源
去收集相关信息,严重的可能会导致业务请求被阻塞,从而引起故障。建议生产上不要频繁
的去查询sys或者performance_schema、information_schema来完成监控、巡检等工作