慢查询的分析以及MYSQL中常用的优化方法

一条SQL执行很慢?

1.执行的时候遇到行锁,表锁

2.没有建索引,或者建了索引没有用到,需要去分析

        怎么判断一个 mysql 中 select 语句是否使用了索引,可以在 select 语句前加上 explain,比如 explain select * from tablename;返回的一列中,若列名为 key 的那列为 null,则没有使用索引,若不为 null,则返回实际使用的索引名。让 select 强制使用索引的语法:select * from tablename from index(index_name);

3.数据库在刷新脏页,例如redolog写满了需要同步到磁盘。

MySQL 数据库有一个“慢查询日志”功能,用来记录查询时间超过某个设定值的SQL。

慢查询日志功能开启:

slow_query_log :是否开启慢查询日志功能(必填)

long_query_time :超过设定值,将被视作慢查询,并记录至慢查询日志文件中(必填)

log-slow-queries :慢查询日志文件(不可填),自动在 data 创建一个 [hostname]-slow.log 文件

配置:

set global slow_query_log = ON;   # 开启这个功能
 
set GLOBAL long_query_time = 1;# 设置查询“超时”时间
当我们开启这个功能时候每一条sql语句如果执行时间超过了设置的时间,就会被写入到日志文件里面
在定位到我们有问题的sql文件以后:

如何对我们的sql语句进行优化呢

1.选择最适用的字段类型(数据库中的表越小,在它上面执行的查询也就会越快)

比如一个字符串,能够确定长度,就用char(6)而不要用char(255)这样给数据库增加不必要的空间,还有varchar长度是动态可变的(它更节省空间),char的效率比varchar快。

高重复的字段将其转换为数值类型。数值类型的处理比文本类型快了很多。

对于字段比较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表。因为当一个表的数据量很大时,会由于使用频率低的字段的存在而变慢。

2.使用连接(JOIN)来代替子查询(Sub-Queries)

  连接(JOIN)之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

  但是大量的使用join也不是好的办法:

  很多高性能的应用都会对关联查询进行分解,就是可以对每一个表进行一次单表查询,然后将查询结果在应用程序中进行关联,很多场景下这样会更高效。

3.使用UNION来替换手动创建临时表

4.多用事务:

要把某个数据同时插入两个相关联的表中,可能会出现这样的情况:第一个表中成功更新后,数据库突然出现意外状况,造成第二个表中的操作没有完成,这样,就会造成数据的不完整,甚至会破坏数据库中的数据。

5.使用外键来保证数据的关联性。

外键可以保证每一条销售记录都指向某一个存在的客户。在这里,外键可以把customerinfo表中的customerid映射到salesinfo表中customerid,任何一条没有合法customerid的记录都不会被更新或插入到salesinfo中。

CREATE  TABLE   customerinfo( customerid   int primary key) engine = innodb;

CREATE  TABLE   salesinfo( salesid int not null,customerid  int not null, primary key(customerid,salesid),foreign key(customerid)  references  customerinfo(customerid) on delete cascade)engine = innodb;
注意例子中的参数“on delete cascade”。该参数保证当customerinfo表中的一条客户记录被删除的时候,salesinfo表中所有与该客户相关的记录也会被自动删除。如果要在MySQL中使用外键,一定要记住在创建表的时候将表的类型定义为事务安全表InnoDB类型。该类型不是MySQL表的默认类型。定义的方法是在CREATE TABLE语句中加上engine=INNODB。如例中所示。

6.对于limit分页的优化:

第一种使用有索引的列来做order by操作。
第二种limit 2000很慢怎么解决?
1.传入上一次查询的id,对limit的优化,不是直接使用limit,而是首先获取到offset的id(通过这个id来做一遍过滤),然后直接使用limit size来获取数据。

 ege:select id,name,content from users order by id asc limit 2000,20  一共会扫描2020行

             可是当我们记录上次最大id    select id,name,content from users where id>2000 order by id asc limit 20   就只用扫描20行(主要还是利用到了id索引,找到第2000个只需要O(log2n的时间复杂度))

2.select id,name,content from users where id>=(select id from users limit 2000,1) limit 20;

3.join子查询连接

select id,name,content from users a join (select id from users limit 2000,20) b on a.id=b.id; 

      通过反向查询 order by id des limit (count-2020),20

       如果有where条件需要建立联合索引

limit分页优化参考:https://uule.iteye.com/blog/2422189

SELECT * FROM user LIMIT 10000,1.对于它对于优化?

1.select *很有可能返回不需要的列,如果可以指定具体需要哪些列会更好,减少结果集整体大小。
2.limit 10000,1,分不同情况的优化。
对于user表主键id是连续的,可以改写select id,username,age from user where id > 10000 limit 1(利用自增索引)
对于user表主键id有断层的,对于select * from user limit 10000,1是没有优化空间的。

常见的查询就是根据索引先获得主键id,再根据这个主键id去主键索引树里面去查询:

Select * From table_name Where id in (Select id From table_name where ( user = xxx )) limit 10000, 10;

select * from table_name where( user = xxx ) limit 10000,10
  • 子查询只用到了索引列,没有取实际的数据,所以不涉及到磁盘IO,所以即使是比较大的 offset 查询速度也不会太差。
  • 利用子查询的方式,把原来的基于 user 的搜索转化为基于主键(id)的搜索,主查询因为已经获得了准确的索引值,所以查询过程也相对较快。

3.在大多数情况下,主键id很有可能是断层的,而且假设实际需求就是select * from user limit 10000,1,加了where条件就不符合需求。那么这种情况下,就只能对表结构和表数据下手了,想办法将行数据大小减少,例如恰当地将varchar换成int,按照三范式减少数据冗余等,目的是使得每一个数据页能装下更多的行数据,即使全表扫描,读取尽可能少的数据页。

4.从SELECT * FROM user LIMIT 10000,1来看,就是简单的获取user表第10001条数据,看不到有什么实质的查询意义,而且对于select id,username from user limit 10000,1,这样在username列上有索引和无索引会使得返回结果集有可能是不一样的,应该结合业务逻辑来看看有没有优化空间。

7.使用索引注意项:

负向查询不能使用索引(尽量避免使用 != 或 not in或 <> 等否定操作符)

select name from user where id not in (1,3,4);

应该修改为:

select name from user where id in (2,5,6);

 

前导模糊查询不能使用索引

如:

select name from user where name like '%zhangsan'

非前导则可以:

select name from user where name like 'zhangsan%'

建议可以考虑使用 Lucene 等全文索引工具来代替频繁的模糊查询。

 

数据区分不明显的不建议创建索引

如 user 表中的性别字段,可以明显区分的才建议创建索引,如身份证等字段。

 

字段的默认值不要为 null

这样会带来和预期不一致的查询结果。

 

在字段上进行计算不能命中索引(不要在列上使用函数)

select name from user where FROM_UNIXTIME(create_time) < CURDATE();

应该修改为:

select name from user where create_time < FROM_UNIXTIME(CURDATE());

 

尽量避免使用 or 来连接条件

最左前缀问题

如果给 user 表中的 username pwd 字段创建了复合索引那么使用以下SQL 都是可以命中索引:

select username from user where username='zhangsan' and pwd ='axsedf1sd'

select username from user where pwd ='axsedf1sd' and username='zhangsan'

select username from user where username='zhangsan'

但是使用

select username from user where pwd ='axsedf1sd'

是不能命中索引的。

 

如果明确知道只有一条记录返回

select name from user where username='zhangsan' limit 1

可以提高效率,可以让数据库停止游标移动。

 

不要让数据库帮我们做强制类型转换

select name from user where telno=18722222222

这样虽然可以查出数据,但是会导致全表扫描。

需要修改为

select name from user where telno='18722222222'

如果需要进行 join 的字段两表的字段类型要相同

不然也不会命中索引。

覆盖索引的好处

如果一个索引包含所有需要的查询的字段的值,直接根据索引的查询结果返回数据,而无需读表,能够极大的提高性能。因此,可以定义一个让索引包含的额外的列,即使这个列对于索引而言是无用的。

如果建索引的字段为null我们也是可以用索引的。

说一张表里有1千万条数据,有一个字段status有两个值(1待审核、2审核通过),然后呢有两个列表即待审核列表与审核通过的列表,那么如何优化查询SQL使其列表的查询速度达到最快?

1.分表,为0的存在一个表里面,为1的存在另外一个表里面。

2.把它全部读取到Redis的bitmap里面,可以很快的查询到这些数据。

原文地址:https://www.cnblogs.com/yangfei123/p/12651516.html