Mysql sql优化
一 定位慢查询
1.1 查询mysql的基本状态
- 查询数据库的状态
SHOW STATUS
- 启动时间
SHOW STATUS LIKE 'uptime'
- 基本的数据库操作次数
SHOW STATUS LIKE 'Com_insert';
SHOW STATUS LIKE 'Com_delete';
SHOW STATUS LIKE 'Com_select';
SHOW STATUS LIKE 'Com_update';
- 显示试图连接mysql数据库的次数
SHOW STATUS LIKE 'connections';
- 显示慢查询次数
SHOW STATUS LIKE 'slow_queries';
1.2 设置慢查询并定位
(1)首先向表里插入1000000条数据
创建存储过程
DELIMITER $$
USE `test`$$
DROP PROCEDURE IF EXISTS `insertable`$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `insertable`()
BEGIN
SET @x1=0;
WHILE @x1<1000000 DO
SET @x1=@x1+1;
INSERT INTO test(field1,field2,field3,field4,field5)
VALUES(@x1,@x1,@x1,@x1,@x1);
END WHILE;
END$$
DELIMITER ;
(2)其次是查询慢查询时间
如上图所示我设置的查询时间是0.009ms
(3)设置慢查询时间
SET long_query_time=0.009;
(4)查询慢查询次数
SHOW STATUS LIKE 'slow_queries';
(5)设置慢查询日志
[root@bogon ~]# vim /etc/my.cnf
(6)方法一:全局变量设
将 slow_query_log 全局变量设置为“ON”状态
mysql> set global slow_query_log='ON';
设置慢查询日志存放的位置
mysql> set global slow_query_log_file='/usr/local/mysql/data/slow.log';
查询超过1秒就记录
mysql> set global long_query_time=1;
(7)方法二:配置文件设置
修改配置文件my.cnf,在[mysqld]下的下方加入
[mysqld]
lower_case_table_names=1
slow_query_log = ON
slow_query_log_file = /var/lib/mysql/slow.log
long_query_time = 0.005
datadir=/var/lib/mysql
在上图所示的位置,配置慢查询日志与慢查询时间,慢查询日志的mysql的sql语句的路径。如上图所示这样就配置好了,这里要注意的是要想获取到慢查询日志,就必须要让配置的慢查日志路径有mysql用户的写权限才行。Mysql默认的权限路径是/var/lib/mysql路径下。所以可以把慢查询日志放到这个路径下面。
重启mysql数据库 service mysqld start
设置完以后可以检查一下慢查询时间是否生效
SHOW VARIABLES LIKE 'slow_query%';
(7)定位慢查询的sql语句
1 执行查询语句
SELECT * FROM test;
SELECT SLEEP(2);
2 到日志目录下找执行语句
如上图所示就是慢查询日志的定位方式。
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
SELECT SLEEP(2);
SHOW STATUS LIKE 'slow_queries';
(8)查询连接进程的状态
SHOW PROCESSLIST
二 优化sql语句
2.1执行计划
上面已经讲了如何定位到查询慢的sql语句了,那么接下来应该就从提取出来的慢的sql,进行分析了。这个时候一个重要的概念就来了sql执行计划。
EXPLAIN SELECT * FROM test ORDER BY NULL;
如上所示的sql
我们可以看到这条查询语句的执行情况。
select_type:表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接 或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或 者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等。
table:输出结果集的表。
type:表示表的连接类型,性能由好到差的连接类型为 system(表中仅有一行,即 常量表)、const(单表中最多有一个匹配行,例如 primary key 或者 unique index)、 eq_ref(对于前面的每一行,在此表中只查询一条记录,简单来说,就是多表连接 中使用primary key或者unique index)、ref (与eq_ref类似,区别在于不是使用primary key 或者 unique index,而是使用普通的索引)、ref_or_null(与 ref 类似,区别在于 条件中包含对 NULL 的查询) 、index_merge(索引合并优化)、unique_subquery(in 的后面是一个查询主键字段的子查询)、index_subquery(与 unique_subquery 类似, 区别在于 in 的后面是查询非唯一索引字段的子查询)、range(单表中的范围查询)、 index(对于前面的每一行,都通过查询索引来得到数据)、all(对于前面的每一行,都通过全表扫描来得到数据)。
possible_keys:表示查询时,可能使用的索引。
key:表示实际使用的索引。
key_len:索引字段的长度。
rows:扫描行的数量。
Extra:执行情况的说明和描述。
如上所示的这些个字段,展示了sql语句的实际执行情况。
Type字段可以通过类型来区分这条查询与句的性能好坏。比如
All<index<range<ref<eq_ref<const,system<NULL
如上所示是索引type的表示,从左到右性能依次由差到最好
Type=all,代表全表扫描,遍历表中的每一行
Type=index, 代表索引全扫描,mysql遍历整个索引来查询匹配行
Type=rang,索引范围扫描,常见的有<,<=,>,>=,between
Type=ref,使用非唯一索引扫描或唯一索引前缀扫描,返回匹配某个单独值的记录行。
Type=eq_ref,类似ref,区别就在使用的索引是唯一索引,对于每个索引健值,表中只有一条记录匹配,简单来说就是多表连接中使用primary key或者unique index作为关联条件来查询。
Type=const/system 单表中最多有一个匹配行,查询起来非常迅速,所以这个匹配行中的其他列的值可以被查询优化器在当前查询中当做常量来处理。
Type=null ,mysql不用访问表或者索引,直接就能得到结果。Explain select 1 from dual
2.2索引
MyISAM 存储引擎的表的数据和索引是自动分开存储的,各自是独立的一个文件;InnoDB 存储引擎的表的数据和索引是存储在同一个表空间里面,但可以有多个文件组成。
MySQL 中索引的存储类型目前只有两种(BTREE 和 HASH),具体和表的存储引擎相关: MyISAM 和 InnoDB 存储引擎都只支持 BTREE 索引;MEMORY/HEAP 存储引擎可以支持 HASH 和 BTREE 索引。通过为表建立索引,可以大大提高系统的查询性能。
2.3索引使用
2.3.1 前期准备
1 创建test表
2为test表添加索引
ALTER TABLE test ADD INDEX retal(rental_date,invertor_id,customer_id)
2.3.2 匹配全值
对索引中每一列都指定了具体值,即对索引中所有列都有等值匹配。
如上所示type表示普通索引,使用了我们创建的索引。而且是唯一锁定了一行。
2.3.3 范围索引
EXPLAIN SELECT * FROM test WHERE rental_date >= '2018-10-11 15:53:25' AND customer_id < '2019-10-11 15:53:25';
当按照时间范围查询的时候,它将按照范围索引进行查询,当然如果是联合索引,那么就会使用最左匹配原则,只有最左索引能使用范围查询。
2.3.4 最左匹配
EXPLAIN SELECT * FROM test WHERE rental_date='2018-10-11 15:53:25' AND field2=1;
EXPLAIN SELECT * FROM test WHERE invertor_id=2 AND field2=1;
如上图所示的两个sql语句,只有联合索引中的第一个字段配合其他字段的时候才会显示使用了索引,如果不是第一个字段就不会使用索引。
2.3.5 索引字段
当查询的字段也在索引中的时候查询效率会更高。
EXPLAIN SELECT invertor_id FROM test WHERE rental_date='2018-10-11 15:53:25';
如上图所示当查询的字段与条件字段都在索引中的时候会看到extra字段显示了Using index也就是只使用了索引的意思,这样效果会更好.
2.3.6 匹配列前缀
ALTER TABLE test ADD INDEX (title)
EXPLAIN SELECT field1 FROM test WHERE title LIKE 'xxx%';
如上所示我们取索引列中的第一列,和索引列中的几个字符就可以实现列前缀索引
2.3.7 索引匹配部分精确
EXPLAIN SELECT invertor_id FROM test WHERE rental_date='2018-10-11 15:53:25' AND customer_id>=10 AND customer_id<29;
如上图所示,索引使用的是时间索引,然后又由于查询的是索引字段,所以最后extra使用的是using index也就是精确定位到了具体的索引。
2.3.8 列索引那么column name is null
EXPLAIN SELECT * FROM test WHERE field2 IS NULL;
如上所示,当列上创建了索引的时候,控判断的时候也会用到索引
2.3.9 存在索引但不能使用索引
EXPLAIN SELECT * FROM `test` WHERE field2 LIKE "%fa%";
如上所示 我们对field2是加了索引的,但是为什么我们使用像上面这样的方式进行查询的时候却没有索引了,原因就在于这样的方式实际上是只使用左前缀索引的。那么它自然效率就相对比较低。
ALTER TABLE test ADD INDEX xs(field1,field2);
EXPLAIN SELECT * FROM (SELECT field1 FROM test WHERE field2 LIKE "%fa%") a,
test b WHERE a.field1 =b.`field1`;
理想情况下通过二级索引,速度或许会更快一点。但是实际上这样的查询的性能,经本人测试却还比不上不加二级索引的效率。
2.4.10 数据类型出现隐式转换的时候不会使用索引
EXPLAIN SELECT field1 FROM test WHERE field2 =1;
EXPLAIN SELECT field1 FROM test WHERE field2 ='1';
如上两行sql所示的,如果因为数据类型的问题导致你都能查到结果,但是有隐式的数据类型转换。这样就使得索引失效了。
2.4.11 复合索引不满足最左索引原则是不会使用索引的
ALTER TABLE test ADD INDEX indexfs(rental_date,invertor_id,customer_id);
EXPLAIN SELECT * FROM test WHERE invertor_id='1'
如上图所示展示的是索引的情况,如果建立了联合索引的情况下。如果不使用
最左索引,那么对于其他索引字段是失效的。
2.4.2 使用索引比全表扫描慢不使用索引
2.4.3 or 前后都得使用索引
EXPLAIN SELECT * FROM test WHERE customer_id='' OR field2 =1;
EXPLAIN SELECT * FROM test WHERE customer_id='' OR field3 =1;
由图可以看出来,如果用or的条件,必须前后都使用索引,要不然就不会使用索引了。
2.4大批量插入数据的优化
(1)按照主键的排列顺序摆放好了再导入可以有效的提高导入的效率。
(2)在导入数据时执行set unique_checks=0,关闭唯一性检查,在导入结束后执行set unique_checks=1,恢复唯一性校验,可以提高导入效率。
(3)在导入前执行set autocommit=0,关闭自动提交,导入结束后再执行set autocommit=1,打开自动提交。
2.5优化insert语句
(1)批量插入的时候,可以先备份索引文件,删除索引再,重新插入,插入完成以后,再添加索引
(2)使用多值插入insert into table1 value(1,2,3,4,5…….)
2.6 排序优化
(1) 使用索引直接排序
EXPLAIN SELECT invertor_id FROM test ORDER BY rental_date DESC
如上图所示是直接使用了索引进行排序。只使用索引不需要额外的排序,操作效率高。
(2) 对返回数据,filesort
EXPLAIN SELECT invertor_id FROM test ORDER BY field2 DESC
(3) 用过索引加filesort联合排序
EXPLAIN SELECT invertor_id,rental_date,customer_id FROM test ORDER BY customer_id DESC
如上图所示原本已经是完全在索引上就可以进行一次排序了,可以使用的不是最左字段,所以又用filesort对索引进行了一个二次排序。所以在使用联合索引进行排序的时候,这里就要注意了。我们可以在排好序的索引字段上直接使用就可以了。改上面的sql语句,或者更改索引创建的方式。
EXPLAIN SELECT invertor_id,rental_date,customer_id FROM test ORDER BY rental_date DESC
总结
1 对应与索引的排序如果是where 与order by 都能够使用索引里面的字段那是最好的
2 对于排序字段最好后面不管有多少排序字段都尽量使用索引里面包含的字段,这样就可以通过索引来完成排序工作。
3 可以通过创建合适的索引来减少filesort的出现。减少对不必要的数据重复排序。对于优化filesort排序的方式,首先filesort排序有两种一种是内存加临时表排序。一种是纯内存排序。纯内存排序可以提高我们的排序性能,减少磁盘io.可以调整系统参数max_length_for_sort_data来加大内存这样就可以减少IO,提高排序效率。不过也要适当。
2.7 优化group by
EXPLAIN SELECT * FROM test GROUP BY invertor_id;
如上所示的语句,在默认情况下当使用了group by语句后,mysql是默认对group by后面的字段进行排序的。那么我们为了避免group by语句进行排序
EXPLAIN SELECT * FROM test GROUP BY invertor_id ORDER BY NULL;
通过order by null就可以避免filesort了。
2.8 子查询优化
尽量使用连接查询,来代替子查询。因为子查询需要创建临时表。而连接不需要创建临时表,连接查询的效率要远远高于子查询的效率。
2.9 分页优化
1 在索引上完成排序和分页的操作,再连接主表进行查询
EXPLAIN SELECT * FROM test ORDER BY field2 DESC LIMIT 10000,20
如上所示全表扫描。
EXPLAIN SELECT * FROM test a INNER JOIN(SELECT field1 FROM test ORDER BY field2 DESC LIMIT 10000,20) b ON a.field1=b.field1;
通过索引以后再做内连接查询。但是如上所示我们看到我们又通过内存进行排序了,那么怎么解决呢,我们可以通过建索引的方式来提高查询效率 我们可以通过索引主键来排序,这样就可以先通过索引排好序再去查表。
ALTER TABLE test ADD INDEX index_test(field2)
2.10 通过参数记录最后一条的id
开发的时候,通过查询参数的方式,记录下分页后的最后一条记录,这样就可以通过explain SELECT * FROM test WHERE field1 <10010 ORDER BY field1 DESC LIMIT 10,也可以通过这种方式来实现跳页面还有分页。