MySQL优化
一:SQL性能分析
SQL执行频率
对于MySQL的优化,主要是对其查询语句进行优化。我们可以先使用以下指令查看一下 SQL 的执行频率,可以查看到当前数据库的 insert、update、delete、select的访问次数。
show global status like 'com_______'; #(7个下划线)
结果:
mysql> show global status like 'com_______';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_binlog | 0 |
| Com_commit | 0 |
| Com_delete | 0 |
| Com_insert | 0 |
| Com_repair | 0 |
| Com_revoke | 0 |
| Com_select | 11 |
| Com_signal | 0 |
| Com_update | 0 |
| Com_xa_end | 0 |
+---------------+-------+
10 rows in set (0.00 sec)
每使用一条语句 value 值都会加1。
慢查询日志
慢查询日志是记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认为10秒)的所有SQL语句的日志。当语句执行时间超过了指定时间,会被认定为为慢查询。MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置。
查看开启情况
show variables like 'slow_query_log';
执行结果
mysql> show variables like 'slow_query_log';
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | OFF |
+----------------+-------+
1 row in set (0.00 sec)
开启慢查询日志
在文件 my.cnf 中配置
slow_query_log=1 #开启 MySQL 慢日志查询
long_query_time=2 #设置慢日志时间为2秒
profile详情
使用show profiles 指令能在做SQL优化时帮助我们了解时间都耗费到哪里去了。
查看MySQL是否支持profile操作
select @@have_profiling;
执行结果
mysql> select @@have_profiling;
+------------------+
| @@have_profiling |
+------------------+
| YES |
+------------------+
1 row in set (0.01 sec)
profiles默认是关闭的,开启指令:
set profiling=1;
执行一系列的业务SQL的操作,通过以下指令查看指令的执行耗时。
show profiles; #查看每一条SQL的耗时基本情况
show profile for query query_id; #查看指定 query_id 的SQL语句各个阶段的耗时情况
show profile cpu for query query_id; #查看指定query_id的SQL语句CPU的使用情况
explain执行计划
使用 explain 指令可以查看SQL的执行计划
explain select * from tb_user;
首先需要了解一下各个字段的含义:
- id:
select查询的序列号,表示查询中执行select字句或者是操作表的顺序(id相同,执行顺序从上到下,id不同,值越大,越先执行)。因为可能是多表查询,所以会造成 id 相同。 - select_type:
表示查询的类型,分别有 simple(简单表,即不使用表连接或子查询),primary(主查询,即外层的查询),union(union中的第二个或者后面的查询语句),subquery(select/where之后包含了子查询)。 - type:
表示索引类型,对type优化的前提是有索引。性能由好到差得到连接类型为:NULL>system>const>eq_ref>ref>range>index>all
NULL:除非不涉及表查询,否则不会出现
system:查询系统表的时候
const:访问主键或者唯一索引
eq_ref:唯一性索引,返回匹配唯一行数据,常用于唯一索引和主键索引
ref:使用非唯一性的索引访问,对于每个索引键的查询,返回匹配的所有行
range:检索指定范围的行
index:使用了索引,但是扫描整个索引树
all:全表扫面 - prossible_key:
显示可能应用在这张表上的索引,一个或多个,NULL则为没有用索引。 - key:
实际使用的索引,如果为NULL,则没有使用索引 - Key_len:
表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际长度,在不损失精确性的前提下,长度越短越好。 - ref:
指明当前表所参照的字段,必须两个都有索引才会指明,否则为null或者const - rows:
MySQL认为必须要执行查询的行数,在InnoDB引擎表中,是一个估计值,可能并不总是准确的。 - extra:
1.Using filesort:性能消耗大,需要额外的一次排序(查询),常见于 order by。通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer中完成排序操作,对于单索引,如果排序和查找不是同一个字段时出现
2.Using index:性能提示:索引覆盖。不读取原文件,只从索引文件中获取数据,不需要回表查询。通过有序索引顺序扫描直接返回有序数据,这种情况即为Using index,不需要额外的排序,操作效率高。
二:优化
对主要对以下常见的语句进行优化分析。
- 插入优化
- 主键优化
- order by优化
- group by优化
- count 优化
- limit 优化
- update 优化
插入优化
通常我们执行插入语句都是一行一行执行进行,由于每条语句都要与数据库进行连接进行网络传输,效率是比较低的。
insert into tb_user(id,name) value(1,'zhangsan');
insert into tb_user(id,name) value(2,'lisi');
insert into tb_user(id,name) value(3,'wangwu');
优化:
1.建议批量插入,最好数量在500-1000条之间
insert into tb_user(id,name) value(1,'zhangsan'),(2,'lisi'),(3,'wangwu');
2.手动提交事务 start transaction//开启事务 commit//提交
start transaction;
insert into tb_user(id,name) value(1,'zhangsan'),(2,'lisi'),(3,'wangwu');
insert into tb_user(id,name) value(4,'zhangsan'),(5,'lisi'),(6,'wangwu');
insert into tb_user(id,name) value(7,'zhangsan'),(8,'lisi'),(9,'wangwu');
commit;
3.主键顺序插入
主键乱序插入:8 1 9 21 88 2 4 15 ...
主键顺序插入:1 2 4 8 9 15 21 88 ...
一次性插入大批量数据时,使用insert语句插入性能较低,可以使用MySQL数据库提供的 load 指令进行插入。
#客户端连接服务器时,加上参数 --local-infile
mysql --local-infile -u root -p;
#设置全局参数local_infile为1,开启从本地加载文件导入数据的开关
set global local_infile=1;
#执行load指令将准备好的数据,加载到表结构中
load data local infile '/root/sql/log' into table 'tb_user' fileds terminated by ',' lines terminated by '\n';
主键优化
在 InnoDB 存储引擎中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(index organized table IOT)。
- 页分裂
当插入数据时,会发生页分裂,InnoDB中使用页来存储数据,页也可以为空,也可以填充一半,也可以全部填充。每个页包含了2-N行数据,根据主键排列。 - 页合并
当删除数据时,实际上记录并没有被物理删除,只是记录被标记为删除并且它的空间变得允许被其他记录声明使用。当页中删除的记录达到(默认为页的50%),InnoDB会开始寻找最靠近的页看看是否可以将两个页合并以优化空间使用。 - 优化操作:
1.在满足业务需求的情况下,尽量降低主键的长度
2.插入数据时,尽量选择顺序插入,选择使用AUTO_INCREMENT自增主键
3.尽量不要使用UUID做主键或者是其他自然主键,如身份证号
4.业务操作时,避免对主键的修改
order by优化
- 优化操作:
1.根据排序字段建立合适的索引多字段排序时,也遵循最左前缀法则。
2.尽量使用覆盖索引。
3.多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(asc.desc)。
4.如不可避免出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认为256K)。
#创建索引
create index idx_user_age_phone_aa on tb_user(age,phone);
#创建索引后,根据age,phone进行升序排序
explain select id,age,phone from tb_user order by age,phone;
#创建索引后,根据age,phone进行降序排序
explain select id,age,phone from tb_user order by age desc,phone desc;
group by 优化
- 优化操作
1.分组操作时,可以通过索引来提高效率。
2.分组操作时,索引的使用也是满足最左前缀法则的。
limit 优化
当数据量非常大的时候,一个常见的问题是 limit 10000000,10,此时需要MySQL排序前10000010条记录,仅仅返回10000000-10000010的记录,其他记录丢弃,查询排序代价非常大。
- 优化操作
1.一般分页查询时,通过创建覆盖索引能够比较好的提高性能,可以通过覆盖索引加子查询形式进行优化。
explain select * from tb_user t,(select id from tb_user order by id limit 10000000,10) a where t.id = a.id;
count 优化
count()是一个聚合函数,对于返回的结果集,会一行行的判断,如果count函数的参数不是NULL,累计值就加1,否则不加,最后返回累计值。
count的几种用法:
1.count(主键)
InnoDB引擎会遍历整张表,把每一行的主键id值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加。
2.count(字段)
没有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
有not null约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接进行累加。
3.count(1)
InnoDB引擎遍历整张表,但不取值,服务层对于返回的每一行,放一个数字 ”1“ 进去,直接按行进行累加。
4.count(*)
InnoDB引擎并不会把全部字段取出来,而是做了专门的优化,不取值,服务层直接按行进行累加。
按效率排序:count(字段) < count(主键id) < count(1) < count(),所以尽量使用count()
update 优化
InnoDB引擎的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引元素不能失效,否则会从行锁升级为表锁
#id 默认使用了主键索引
update tb_user set name = 'lisi' where id = 1;
update tb_user set name = 'zhangsan' where id = 'lisi';