MySQL随着数据量的增多,遇到SQL执行缓慢的问题越来越多。文中介绍几种常用的SQL优化的一般步骤.
- show status
- explain
- show profile
- trace
show status
show status 主要是查看各种类型的操作的执行次数。
SHOW STATUS LIKE 'COM%';
Variable_name Value
Com_admin_commands 0
Com_alter_db 0
Com_alter_db_upgrade 0
Com_alter_event 0
Com_alter_function 0
Com_alter_procedure 0
Com_alter_server 0
Com_alter_table 0
Com_alter_tablespace 0
Com_analyze 0
Com_assign_to_keycache 0
Com_begin 0
Com_binlog 0
Com_call_procedure 0
Com_change_db 1
Com_change_master 0
Com_check 0
Com_checksum 0
Com_commit 0
Com_compound_sql 0
Com_create_db 0
Com_create_event 0
Com_create_function 0
Com_create_index 0
Com_create_procedure 0
Com_create_role 0
Com_create_server 0
Com_create_table 0
Com_create_temporary_table 0
Com_create_trigger 0
Com_create_udf 0
Com_create_user 0
Com_create_view 0
Com_dealloc_sql 0
Com_delete 0
Com_delete_multi 0
Com_do 0
Com_drop_db 0
Com_drop_event 0
Com_drop_function 0
Com_drop_index 0
Com_drop_procedure 0
Com_drop_role 0
Com_drop_server 0
Com_drop_table 0
Com_drop_temporary_table 0
Com_drop_trigger 0
Com_drop_user 0
Com_drop_view 0
Com_empty_query 0
Com_execute_sql 0
Com_flush 0
Com_get_diagnostics 0
Com_grant 0
Com_grant_role 0
Com_ha_close 0
Com_ha_open 0
Com_ha_read 0
Com_help 0
Com_insert 0
Com_insert_select 0
Com_install_plugin 0
Com_kill 0
Com_load 0
Com_lock_tables 0
Com_optimize 0
Com_preload_keys 0
Com_prepare_sql 0
Com_purge 0
Com_purge_before_date 0
Com_release_savepoint 0
Com_rename_table 0
Com_rename_user 0
Com_repair 0
Com_replace 0
Com_replace_select 0
Com_reset 0
Com_resignal 0
Com_revoke 0
Com_revoke_all 0
Com_revoke_role 0
Com_rollback 0
Com_rollback_to_savepoint 0
Com_savepoint 0
Com_select 0
Com_set_option 2
Com_show_authors 0
Com_show_binlog_events 0
Com_show_binlogs 0
Com_show_charsets 0
Com_show_collations 0
Com_show_contributors 0
Com_show_create_db 0
Com_show_create_event 0
Com_show_create_func 0
Com_show_create_proc 0
Com_show_create_table 0
Com_show_create_trigger 0
Com_show_databases 1
Com_show_engine_logs 0
Com_show_engine_mutex 0
Com_show_engine_status 0
Com_show_errors 0
Com_show_events 0
Com_show_explain 0
Com_show_fields 1
Com_show_function_status 0
Com_show_generic 0
Com_show_grants 0
Com_show_keys 0
Com_show_master_status 0
Com_show_open_tables 0
Com_show_plugins 0
Com_show_privileges 0
Com_show_procedure_status 0
Com_show_processlist 0
Com_show_profile 0
Com_show_profiles 0
Com_show_relaylog_events 0
Com_show_slave_hosts 0
Com_show_slave_status 0
Com_show_status 2
Com_show_storage_engines 0
Com_show_table_status 0
Com_show_tables 1
Com_show_triggers 0
Com_show_variables 1
Com_show_warnings 0
Com_shutdown 0
Com_signal 0
Com_start_all_slaves 0
Com_start_slave 0
Com_stmt_close 0
Com_stmt_execute 0
Com_stmt_fetch 0
Com_stmt_prepare 0
Com_stmt_reprepare 0
Com_stmt_reset 0
Com_stmt_send_long_data 0
Com_stop_all_slaves 0
Com_stop_slave 0
Com_truncate 0
Com_uninstall_plugin 0
Com_unlock_tables 0
Com_update 0
Com_update_multi 0
Com_xa_commit 0
Com_xa_end 0
Com_xa_prepare 0
Com_xa_recover 0
Com_xa_rollback 0
Com_xa_start 0
Compression OFF
看几个常用的com_update、com_select、com_insert、com_delete,这些CRUD的次数统计能直观的看到数据库服务是读多、还是写多。可根据具体的业务指定不同的方案。
当然对于innodb存储引擎,可查看其对应的信息:
SHOW STATUS LIKE 'innodb_rows_%';
Variable_name Value
-------------------- -------------
Innodb_rows_deleted 933911
Innodb_rows_inserted 1715972
Innodb_rows_read 30210424768
Innodb_rows_updated 235151
表示对应的操作影响的行数。这样的话,数据库的读多写少的类型会看的更直观。也可以直接查询SESSION_STATUS表来查看mysql的统计信息。
SELECT * FROM information_schema.`SESSION_STATUS` s WHERE s.`VARIABLE_NAME` LIKE 'INNODB_ROWS_%';;
注意区分变量名的大小写。
VARIABLE_NAME VARIABLE_VALUE
-------------------- ----------------
INNODB_ROWS_DELETED 933911
INNODB_ROWS_INSERTED 1716071
INNODB_ROWS_READ 30211179934
INNODB_ROWS_UPDATED 235155
定位慢查询
首先要开启慢查询日志,默认是关闭的。
默认的慢查询时间为10s,超过这个值的sql会记录到慢查询的日志文件中,可对这些SQL进行对应的优化。
Variable_name Value
long_query_time 10.000000
slow_query_log OFF
slow_query_log_file LAPTOP-3T4D6I5F-slow.log
有这两种开启方式:
- 在my.cnf 里 通过 log-slow-queries[=file_name]
- 在mysqld进程启动时,指定--log-slow-queries[=file_name]
我们通过在my.cnf中配置来开启慢查询:
#启用慢查询日志
slow_query_log=1
#指定慢查询文件名称
slow-query-log-file=mysql-slow.log
#慢查询时间,超过这个值的会记录到慢查询日志中
long_query_time=1
#SQL语句检测的记录数少于设定值的语句不会被记录到慢查询日志,即使这个语句执行时间超过了long_query_time的阈值
min_examined_row_limit=100
重启数据库,查看配置参数
Variable_name Value
long_query_time 1.000000
slow_query_log ON
slow_query_log_file mysql-slow.log
这样就开启了慢查询日志,通常用mysqldumpslow这个工具来分析慢查询日志信息。
show processlist
慢查询需要等到SQL执行完成后才会写入到日志文件中,要查询当前正在执行的进程、是否锁表等信息慢查询就不行了。需要processlist来查看信息了,当然需要有PROCESS权限才可以看到所有的线程信息,否则只能看到你自己的线程。
show processlit;
Id User Host db Command Time State Info Progress
2 root localhost:10716 \N Query 0 init show processlist 0.000
3 root localhost:10717 \N Sleep 410 \N 0.000
explain
通过explain命令来查看SQL的执行计划。语法很简单,explain 要执行的SQL即可。
EXPLAIN SELECT * FROM sys_role r,sys_user_role ur WHERE r.id= ur.role_id;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE r ALL PRIMARY \N \N \N 1
1 SIMPLE ur ALL \N \N \N \N 1 Using where; Using join buffer (flat, BNL join)
返回的信息详解介绍一下,
id
选择标识符。这是查询中SELECT的序号。如果行引用其他行的并集结果,则该值可以为NULL。在本例中,表列显示了一个值,如<unionM,N>,表示行引用id值为M和N的行的并集。
select_type
- SIMPLE(简单SELECT,不使用UNION或子查询等)
- PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
- UNION(UNION中的第二个或后面的SELECT语句)
- DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
- UNION RESULT(UNION的结果)
- SUBQUERY(子查询中的第一个SELECT)
- DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
- DERIVED(派生表的SELECT, FROM子句的子查询)
- UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
table
涉及的表
type
表示MySQL在表中找到所需行的方式,又称“访问类型”。
常用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)
ALL:全表扫描, MySQL将遍历全表以找到匹配的行
index: 索引全扫描,index与ALL区别为index类型只遍历索引树
range: 索引范围扫描,常见于< 、<= 、>、>=、between等操作。
ref: 使用非唯一索引扫描或唯一索引的前缀扫描。返回匹配某个单独值的记录行。
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system: 单表中最多有一个匹配的行,查询速度很快。所以这个匹配行中的其他列的值可以被优化器在当前查询中当做常量处理。例如使用primary key或者 unique key来查询。
NULL: 表示不用访问表或使用索引就能直接得到结果。
prossible_keys
可能使用到的索引
key
实际使用到的索引,没有使用到索引就是NULL
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)
不损失精确性的情况下,长度越短越好
ref
ref列显示将哪些列或常量与key列中指定的索引进行比较,以便从表中选择行。
如果值是func,则使用的值是某个函数的结果。要查看哪个函数,请在EXPLAIN之后使用SHOW warning查看扩展的EXPLAIN输出。这个函数实际上可能是一个运算符,比如算术运算符。
rows
表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数
Extra
该列包含MySQL解决查询的详细信息,有以下几种情况:
Using where:列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候,表示mysql服务器将在存储引擎检索行后再进行过滤
Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询
Using filesort:MySQL中无法利用索引完成的排序操作称为“文件排序”
Using join buffer:改值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能。
Impossible where:这个值强调了where语句会导致没有符合条件的行。
Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
有时候使用explain不能定位到问题,可配合profile来联合分析。
show profile
SHOW PROFILE和SHOW PROFILES语句显示分析信息,指示在当前会话过程中执行的语句的资源使用情况。
基本语法:
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
}
当然,首先要开启profile.
mysql> SELECT @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+
1 row in set (0.00 sec)
mysql> SET profiling = 1;
Query OK, 0 rows affected (0.00 sec)
查看所有的profiles。
SHOW profiles;
Query_ID Duration Query
30 0.00010284 SET profiling_history_size = 15
31 0.00350268 SHOW STATUS
32 0.00011305 select @@profiling
33 0.00262382 SHOW STATUS
34 0.00048830 select state, round(sum(duration),5) as `duration (summed) in sec` from information_schema.profiling where query_id = 32 group by state order by `duration (summed) in sec` desc
查看指定id的profile,查看具体线程的时间消耗。
SHOW profile FOR QUERY 34;
其中sending data状态是需要大量磁盘io操作的,比较耗费时间。其他的信息参考mysql官网的说明。
trace
MySQL从5.6加入了SQL的跟踪trace。通过trace文件可以看到优化器为何选择这个执行计划而不是其他的。
首先要开启跟踪trace,
-- 开启并且设置数据格式为JSON
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=ON;
-- 设置内存
SET OPTIMIZER_TRACE_MAX_MEM_SIZE=1000000;
查看是否开启
SHOW VARIABLES LIKE '%optimizer_trace%';
Variable_name Value
optimizer_trace enabled=on,one_line=off
optimizer_trace_features greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on
optimizer_trace_limit 1
optimizer_trace_max_mem_size 1000000
optimizer_trace_offset -1
开始执行SQL
mysql> select * from version;
+--------+----------------+----------------------------+
| VER_ID | SCHEMA_VERSION | VERSION_COMMENT |
+--------+----------------+----------------------------+
| 1 | 2.3.0 | Hive release version 2.3.0 |
+--------+----------------+----------------------------+
1 row in set (0.00 sec)
查看跟踪信息,
select * from information_schema.optimizer_trace\G
查看TRACE字段中对应的json信息。
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `version`.`VER_ID` AS `VER_ID`,`version`.`SCHEMA_VERSION` AS `SCHEMA_VERSION`,`version`.`VERSION_COMMENT` AS `VERSION_COMMENT` from `version`"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"table_dependencies": [
{
"table": "`version`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"rows_estimation": [
{
"table": "`version`",
"table_scan": {
"rows": 1,
"cost": 1
} /* table_scan */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [
{
"plan_prefix": [
] /* plan_prefix */,
"table": "`version`",
"best_access_path": {
"considered_access_paths": [
{
"rows_to_scan": 1,
"access_type": "scan",
"resulting_rows": 1,
"cost": 1.2,
"chosen": true
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100,
"rows_for_plan": 1,
"cost_for_plan": 1.2,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": {
"original_condition": null,
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [
{
"table": "`version`",
"attached": null
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"refine_plan": [
{
"table": "`version`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": {
"select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
当然这只是一个简单的查询的跟踪信息,其他更复杂的SQL的跟踪信息会更加详尽,不过大概的结构就是这个样子的。
小结
通过上述步骤,一般可以定位到问题,剩下的就是对症下药。索引优化、批量数据优化、表碎片、具体的SQL操作的优化等等具体情况具体分析。