MySQL性能优化(一)
认识与开始
认识慢查询
对于一个业务查询来说, 性能问题的直观反映是响应时间,对于一个任务来说分析的主要方法就是拆分任务为子任务,找到慢的子任务。
对于一个慢查询来说,在不关注服务器当前的CPU、网络等因素的前提下,响应时间高主要分析以下几个方面:
- 是不是请求了过多的数据
- 查询是否扫描了额外不相关的数据
开始检测慢查询
MySQL较新的版本已经内置提取慢查询的功能:
|
|
开始分析慢查询
官方提供Explain命令用以分析MySQL对于SELECT的执行计划。
|
|
返回结果包含内容:
id: 查询编号
select_type: 查询的具体类型,详见点击
table: 该查询所引用的表
partitions: 该查询匹配到的分区
type: 表示这个表是如何组织到查询中或表是如何进行关联的
- system: 该表只有一行数据
- const: 最多只有一行匹配的数据,在指定使用主键或唯一索引时候,该表的数据则作为常量使用
eq_ref: 对于每个来自于前面的表的行组合,从该表中读取一行。
select * from t_order a,t_order_ext b where a.order_id=b.order_id;
ref: 对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取。
select * from t_order a,t_order_ext b where a.order_id=b.order_id and a.order_id=100;
- fulltext: 查询使用了一个全文索引
ref_or_null: 类似ref,但是MySQL特意为可以为空的字段进行了空值的查询(字段尽量不为空)
select * from t_order where user_id=100 or user_id is null;
- index_merge: 表示使用了索引合并优化方法
unique_subquery: 该类型替换了类似以下IN子查询的 eq_ref:
value IN (SELECT primary_key FROM single_table WHERE some_expr)
index_subquery: 该联接类型类似于unique_subquery。可以替换IN子查询,但只适合下列形式的子查询中的非唯一索引:
value IN (SELECT key_column FROM single_table WHERE some_expr)
- range: 只检索给定范围的行,用一个索引来选择行
- index: 类似ALL,除了索引树被扫描了:
- i:索引覆盖了整个查询,这样的话只扫描索引树就可以返回结果而不需要扫描数据,此时Extra 显示Using index.
- ii: 读取了索引的内容用以进行全表扫描,此时Extra没有Uses index
ALL: 完整的扫描全表,最慢的联接类型,尽可能的避免
select * from xxx
possible_keys: 可能使用到的索引,不一定真正使用到,但如果是NULL则代表无索引可用,需要优化
key: MySQL决定在查询中真正使用的索引,有可能不存在于possible_keys中,原因是此时发现possible_keys中的索引无助于更快的查询,但是所查询的列却完全包含在其他索引中
key_len: 决定使用索引的长度
ref: 那些列或者常量需要和使用到的索引进行比较
rows: MySQL认为的需要完成查询必须检查的行数
filtered: 表示在该查询条件下能够过滤掉数据行数的百分比(实际行数=rows*filtered/100) 5.7.3之前版本需要使用EXTENDED参数才显示,
Extra: 显示关于此查询额外的信息
- Child of ‘table’ pushed join@1: 只在集群模式下存在
- const row not found: 查询的表为空
- Deleting all rows: 对于DELETE语句使用,表示MySQL使用一种快速删除所有数据的方式
- Distinct: MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行
- FirstMatch(tbl_name): 一种semi-join简化策略用在了表上
- Full scan on NULL key: 子查询优化退化策略,此时优化器无法使用索引查找方式
- Impossible HAVING: HAVING语句总是FALSE,无法选择任何数据
- Impossible WHERE: WHERE语句总是FALSE,无法选择任何数据
- Impossible WHERE noticed after reading const tables: MySQL读取了所有的system或者const表,发现where条件永远FALSE
- LooseScan(m..n): semi-join使用到松散扫描策略
- No matching min/max row: select min/max类型的语句没有找到匹配的行
- no matching row in const table: 联表查询中存在空表或没有满足针对唯一索引条件的行
- No matching rows after partition pruning: DELETE或UPDATE,对分区进行剪枝后没有发现要删除或更新的行
- No tables used: 查询没有FROM条件或者存在
FROM DUAL
Not exists: MySQL能够对查询进行LEFT JOIN优化,发现1个匹配LEFT JOIN标准的行后,不再为前面的的行组合在该表内检查更多的行。
SELECT * FROM t1 LEFT JOIN t2 ON t1.id=t2.id WHERE t2.id IS NULL;
此例中,t2的id是主键不可空,所以针对t1当中的每一行记录,MySQL只查t2一次即返回- Plan isn’t ready yet: 使用 EXPLAIN FOR CONNECTION 时候,查询计划还没有结束时
- Range checked for each record(index map: N): MySQL没有发现好的索引,但是发现查询的列在之前的表中已知时有一些索引可用,比没有索引可用要快,N标识可能用到的索引,16进制的掩码(0x19 =>11001标识索引编号1、4、5可能会用到)
- Scanned N databases: 略,关于INFORMATION_SCHEMA表
- Select tables optimized away: 优化器确定只有一行需要返回,为了产生这一行数据需要读取确定行数的数据
- Skip_open_table, Open_frm_only, Open_full_table: 略,关于INFORMATION_SCHEMA表
- Start temporary, End temporary: 表示在semi-join优化中使用了去重策略(Duplicate Weedout)
- unique row not found: 对于SELECT … FROM tbl_name,没有行满足关于unique index或者primary key条件的行
- Using filesort: 此时mysql会根据联接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行。
- Using index: 只需要查找索引树就可以获取所有的行,而不需要做额外的查找
- Using index condition: 需要读取索引进一步判断是否需要读取表所有的行
- Using index for group-by: group by查询中使用索引即可查找到结果
- sing join buffer (Block Nested Loop), Using join buffer (Batched Key Access): 联表查询需要读入前面表作为缓存一部分进行优化查询
- Using MRR: Multi-Range Read优化策略
- Using sort_union(…), Using union(…), Using intersect(…): 标识索引归并是如何进行的
- Using temporary: 使用了中间表
- Using where: 当有where子句时
- Using where with pushed condition: 略
- Zero limit: 存在LIMIT 0 限制无法选择任何数据
在执行EXPLAIN EXTENDED
后紧接着执行SHOW WARNINGS
可输出进过优化器优化处理的SQL提示语句,并不一定可以执行
|
|
得到的优化SQL:
|
|
在生成的结果中可能会有一些特殊的标记,见链接点击查看
简单的方式评估查询性能
查询行数与寻道数的关系:
$$N_{seek}=\frac{\log(行数)}{\log(索引大小/2*3/(索引长度 + 数据指针大小))}$$- 索引大小: 一般是1024 bytes
- 数据指针: 一般是4 bytes
- 索引长度: 500000行一般3bytes
索引存储空间问题:
|
|