Mysql性能优化
Mysql性能优化
性能优化维度
数据库优化思路
应急调优的思路:
针对突然的业务办理卡顿,无法进行正常的业务处理!需要立马解决的场景!
-
show processlist(查看连接session状态)
-
explain(分析查询计划),show index from tableName(分析索引)
-
show status like ‘%lock%’; # 查询锁状态
常规调优的思路:
针对业务周期性的卡顿,例如在每天10-11点业务特别慢,但是还能够使用,过了这段时间就好了。
- 开启慢查询日志,运行一天
- 查看slowlog,分析slowlog,分析出查询慢的语句。
- 按照一定优先级,进行一个一个的排查所有慢语句。
- 分析top sql,进行explain调试,查看语句执行时间。
- 调整索引或语句本身。
##优化实践
查询优化
查看sql语句执行计划Explain
作用
- 查看表的读取顺序
- 查看数据库读取操作的操作类型
- 查看哪些索引有可能被用到
- 查看哪些索引真正被用到
- 查看表之间的引用
- 查看表中有多少行记录被优化器查询
数据准备
create table t1(
id int primary key,
name varchar(20),
col1 varchar(20),
col2 varchar(20),
col3 varchar(20)
);
create table t2(
id int primary key,
name varchar(20),
col1 varchar(20),
col2 varchar(20),
col3 varchar(20)
);
create table t3(
id int primary key,
name varchar(20),
col1 varchar(20),
col2 varchar(20),
col3 varchar(20)
);
insert into t1 values(1,'zs1','col1','col2','col3');
insert into t2 values(1,'zs2','col2','col2','col3');
insert into t3 values(1,'zs3','col3','col2','col3');
create index ind_t1_c1 on t1(col1);
create index ind_t2_c1 on t2(col1);
create index ind_t3_c1 on t3(col1);
create index ind_t1_c12 on t1(col1,col2);
create index ind_t2_c12 on t2(col1,col2);
create index ind_t3_c12 on t3(col1,col2);
查看执行计划
EXPLAIN SELECT * FROM t1;
类型全表扫描(效率低)
type:
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range(尽量保证) > index > ALL
system:表(系统表)中只有一行记录, 这是const类型的特例, 基本上不会出现
const:通过索引一次查询就找到了,const用于比较primary key或者unique索引,该表最多有一个匹配行, 在查询开始时读取。由于只有一行, 因此该行中列的值可以被优化器的其余部分视为常量。const 表非常快, 因为它们只读一次。
explain select * from t1 where id=1
eq_ref:读取本表中和关联表表中的每行组合成的一行。除 了 system 和 const 类型之外, 这是最好的联接类型。当连接使用索引的所有部分时, 索引是主键或唯一非 NULL 索引时, 将使用该值。
explain select * from t1,t2 where t1.id = t2.id;
ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有符合条件的行。
explain select * from t1 where col1='zs1';
range: 只检索给定范围的行, 使用一个索引来选择行.key列显示的是真正使用了哪个索引,一般就是在where条件中使用between,>,<,in 等范围的条件,这种在索引范围内的扫描比全表扫描要好,因为它只在某个范围中扫描,不需要扫描全部的索引
explain select * from t1 where id between 1 and 10;
index: 扫描整个索引表, index 和all的区别为index类型只遍历索引树. 这通常比all快,因为索引文件通常比数据文件小,虽然index和all都是读全表,但是index是从索引中读取,而all是从硬盘中读取数据
explain select id from t1;
all: 全表扫描 ,将遍历全表以找到匹配的行
explain select * from t1;
select_type
- SIMPLE : 简单的select查询,查询中不包含子查询或者UNION
- PRIMARY: 查询中若包含复杂的子查询,最外层的查询则标记为PRIMARY
- SUBQUERY : 在SELECT或者WHERE列表中包含子查询
- DERIVED : 在from列表中包含子查询被标记为DRIVED衍生,MYSQL会递归执行这些子查询,把结果放到临时表中
- UNION: 若第二个SELECT出现在union之后,则被标记为UNION, 若union包含在from子句的子查询中,外层select被标记为:derived
- UNION RESULT: 从union表获取结果的select
索引优化
常见的索引类别
- 普通索引
- 唯一索引
- 主键索引
- 复合索引
- 全文索引
索引存储结构
BTree索引
在前面的例子中我们看见有USING BTREE,这个是什么呢?这个就是MySQL所使用的索引方案,MySQL中普遍使用B+Tree做索引,也就是BTREE。
为什么使用B+树:
B+树是一个多路平衡查找树,它和B树的主要区别在于:
- B树中每个节点(叶子节点和非叶子节点)都存储真实数据。而B+树这种叶子节点存储值,非叶子节点存储键。
- B树中一条记录只会出现一次,不会出现重复。而B+树的键可能出现重复。
- B+树的叶子节点使用双向链表连接。
基于上述特点,B+树具有如下优势:
- 更少的IO次数:B+树的非叶节点只包含键,而不包含真实数据,因此每个节点存储的记录个数比B数多很多(即阶m更大),因此B+树的高度更低,访问时所需要的IO次数更少。此外,由于每个节点存储的记录数更多,所以对访问局部性原理的利用更好,缓存命中率更高。
- 更适于范围查询:在B树中进行范围查询时,首先找到要查找的下限,然后对B树进行中序遍历,直到找到查找的上限;而B+树的范围查询,只需要对链表进行遍历即可。
-**更稳定的查询效率:**B树的查询时间复杂度在1到树高之间(分别对应记录在根节点和叶节点),而B+树的查询复杂度则稳定为树高,因为所有数据都在叶节点。
但是B+树也有其自身的缺点,因为键有可能出现重复,所以会占用更多的空间。但对于现代服务器对比性能来说,空间劣势基本都是可以接受的。
哈希索引
Hash索引在MySQL中使用的并不是很多,目前主要是Memory存储引擎使用,在Memory存储引擎中将Hash索引作为默认的索引类型。所谓Hash索引,实际上就是通过一定的Hash算法,将需要索引的键值进行Hash运算,然后将得到的Hash值存入一个Hash表中。然后每次需要检索的时候,都会将检索条件进行相同算法的Hash运算,然后再和Hash表中的Hash值进行比较并得出相应的信息。
特点:
- Hash索引仅仅只能满足“=”,“IN”和“<=>”查询,不能使用范围查询;
- Hash索引无法被利用来避免数据的排序操作;
- Hash索引不能利用部分索引键查询;
- Hash索引在任何时候都不能避免表扫描;
- Hash索引遇到大量Hash值相等的情况后性能并不一定就会比B+Tree索引高;
索引失效
数据准备
CREATE TABLE staffs (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR (24) NULL DEFAULT '' COMMENT '姓名',
age INT NOT NULL DEFAULT 0 COMMENT '年龄',
pos VARCHAR (20) NOT NULL DEFAULT '' COMMENT '职位',
add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间'
) CHARSET utf8 COMMENT '员工记录表' ;
INSERT INTO staffs(name,age,pos,add_time) VALUES('zhangsan',18,'manager',NOW());
INSERT INTO staffs(name,age,pos,add_time) VALUES('lisi',19,'dev',NOW());
INSERT INTO staffs(name,age,pos,add_time) VALUES('wangwu',20,'dev',NOW());
SELECT * FROM staffs;
ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);
- 全值匹配 (索引idx_staffs_nameAgePos 建立索引时以 name,age,pos 的顺序建立的。全值匹配表示 按顺序匹配的
EXPLAIN SELECT * FROM staffs WHERE name = 'July';
EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND age = 25;
EXPLAIN SELECT * FROM staffs WHERE age = 25 AND name = 'July' AND pos = 'dev';
EXPLAIN SELECT * FROM staffs WHERE age = 25;
EXPLAIN SELECT add_time FROM staffs WHERE age = 25 AND pos = 'dev';
- 最左前缀法则(如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。)
-- 【注意】
-- and 忽略左右关系。既即使没有没有按顺序 由于优化器的存在,会自动优化。
-- 除开上述条件 才满足最左前缀法则。
EXPLAIN SELECT * FROM staffs WHERE age = 25 AND pos = 'dev'; -- 索引失效
EXPLAIN SELECT * FROM staffs WHERE pos = 'dev';-- 索引失效
- 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),如果做的话,会导致索引失效而转向全表扫描
EXPLAIN SELECT * FROM staffs WHERE left(name,4) = 'July';
- 存储引擎不能使用索引中范围条件(between、<、>、in等)右边的列(范围条件右边与范围条件使用的同一个组合索引,右边的才会失效。若是不同索引则不会失效)。
EXPLAIN SELECT * FROM staffs WHERE name = 'July' AND age > 25 AND pos = 'dev';
-
减少select *,使用哪些字段查哪些字段。
-
mysql5.7 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描。但8.0不会。
-
mysql5.7 is not null 也无法使用索引,但是is null是可以使用索引的。但8.0不会
-
like以%开头(‘%abc…’)mysql索引失效会变成全表扫描的操作
-
字符串不加单引号索引失效 ( 底层进行转换使索引失效,使用了函数造成索引失效)(隐式类型转换)