SQL优化
本文介绍一下如何对MySQL进行SQL优化!!
一、优化SQL语句的一般步骤
1、通过show status命令了解各种SQL的执行频率
MySQL客户端连接成功之后,通过show [ session|global ] status命令可以提供服务状态信息。该命令可以根据需要加上参数“session”或者“global”来显示session级(当前连接)的统计结果和global级(自数据库上次启动至今)的统计结果。如果不写,默认使用的参数是“session”
Com_XXX表示每个xxx语句执行的次数,我们比较关心的是以下几个统计参数
Com_select:执行SELECT操作的次数,一次查询只累加1。
Com_insert:执行INSERT操作的次数,对于批量插入的INSERT操作,只累加1。
Com_update:执行UPDATE操作的次数。
Com_delete:执行DELETE操作的次数。
通过以上几个参数,可以很容易地了解当前数据库的应用是以插入更新为主还是以查询操作为主,以及各种类型的SQL大致的执行比例是多少。对于更新操作的次数,是对执行次数的计数,不论提交还是回滚都会进行增加。
对于事务型应用,通过Com_commit和Com_rollback可以了解事务提交和回滚的情况,对于回滚操作非常频繁的数据库,可能意味着应用编写存在问题。
此外,以下几个参数便于用户了解数据库的基本情况。
Connections:试图连接MySQL服务器的次数
Uptime:服务器工作时间
Slow_queries:慢查询的次数(开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能。)
2、定位执行效率较低的SQL语句
(1)慢查询日志
通过慢查询日志定位那些执行效率较低的SQL语句,用--log-slow-queries[ = file_name] 选项启动时,mysqld写一个包含所有执行时间超过long_query_time 秒的SQL语句的日志文件。
查看慢查询相关参数
慢查询设置(方法一)、
将slow_query_log全局变量设置为“ON”状态
set global slow_query_log='ON';
设置慢查询日志存放位置
set global slow_query_log_file='/usr/local/mysql/data/slow.log';
设置查询超过1秒就记录
set global long_query_time=1;
(方法二)
配置文件设置
修改配置文件my.cnf,在[mysqld]下的下方加入
[mysqld] slow_query_log = ON slow_query_log_file = /usr/local/mysql/data/slow.log long_query_time = 1
设置完毕后,记得重启mysql服务
service mysqld restart
测试一下
mysql> select sleep(2);
可以查看到生成慢查询日志。
(2)show processlist
慢查询日志在查询结束以后才记录,所以在应用反应执行效率出现问题的时候查询慢查询日志并不能定位问题,可以使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看SQL的执行情况,同时对一些锁表操作进行优化。
processlist命令的输出结果显示了有哪些线程在运行,不仅可以查看当前所有的连接数,还可以查看当前的连接状态帮助识别出有问题的查询语句等。
如果是root帐号,能看到所有用户的当前连接。如果是其他普通帐号,则只能看到自己占用的连接。showprocesslist只能列出当前100条。如果想全部列出,可以使用SHOW FULL PROCESSLIST命令。
id列,用户登录mysql时,系统分配的"connection_id",可以使用函数connection_id()查看
user列,显示当前用户。如果不是root,这个命令就只显示用户权限范围的sql语句
host列,显示这个语句是从哪个ip的哪个端口上发的,可以用来跟踪出现问题语句的用户
db列,显示这个进程目前连接的是哪个数据库
command列,显示当前连接的执行的命令,一般取值为休眠(sleep),查询(query),连接(connect)等
time列,显示这个状态持续的时间,单位是秒
state列,显示使用当前连接的sql语句的状态,很重要的列。state描述的是语句执行中的某一个状态。一个sql语句,以查询为例,可能需要经过copying to tmp table、sorting result、sending data等状态才可以完成
info列,显示这个sql语句,是判断问题语句的一个重要依据
3、通过EXPLAIN分析低效SQL的执行计划
通过以上步骤查询到效率低的SQL语句后,可以通过EXPLAIN或者DESC命令获取Mysql如何执行SELECT语句的信息,包括在SELECT语句执行过程中表如何连接和连接的顺序
(1)select_type
表示SELECT的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(子查询的第一个SELECT)等
(2)table
输出结果集的表
(3)type
表示MySQL在表中找到所需行的方式,或者叫访问类型,常见类型如下图:
从左到右,性能由最差到最好
all:全表扫描,Mysql遍历全表来找到匹配的行
index:索引全扫描,Mysql遍历整个索引来查询匹配的行
range:索引范围查找,常见于< <= > >= between等操作符
ref:使用非唯一索引扫描或者唯一索引的前缀扫描,返回匹配某个单独值的记录行,
比如 select * from payment where customer_id =350,索引idx_fk_costomer_id是非唯一索引,查询条件为等值查询条件customer_id=35,所以扫描类型为ref,
ref还经常出现在join操作中:where a.customer_id = b.customer_id
eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个索引取值,表中只有一条记录匹配;简单滴说,就是多表连接中使用primary key 或者unique index作为关联条件
const:单表中最多有一个匹配行,查询起来非常迅速,所以这个匹配行中的其他列的值可以被优化器在当前查询中当做常量来处理,例如,根据主键primary key或者唯一索引unique index进行的查询。
例如:select * from customer where email='972455766@qq.com'
null:mysql不用访问表或者索引,直接就能得到结果
例如:select 1;
type还有其他类型,大家遇到可以通过查询其他资料来获取相关信息
(4)possible_keys
表示查询时可能使用到的索引
(5)key
表示实际使用的索引
(6)key_len
使用到索引字段的长度
(7)rows
扫描行的数量
(8)extra
执行情况的说明和描述,包含不适合在其他列中显示但是对执行计划非常重要的额外信息
4、通过show profile分析SQL
通过have_profiling参数,能够看到当前mysql是否支持profile,默认profiling是关闭的,可以通过set语句在session级别开启profiling:
通过profile,我们能够更加清楚地了解SQL执行的过程。
例如:查询comment表记录的数量、
查询完毕后,执行show profiles语句,可以看到当前SQL的query ID为2
通过show profile for query 语句能够看到执行过程中线程的每个状态和消耗的时间:
show profile能够在做sql优化时帮助我们了解时间都耗费到哪里去了
5、确定问题并采取相应的优化措施
经过以上步骤,基本就可以确认问题出现的原因。此时用户可以根据情况采取相应的措施,进行优化以提高执行的效率。
二、两个简单实用的优化方法
对于大多数开发人员来说,可能只希望掌握一些简单实用的优化方法,对于更多更复杂的优化,更倾向于交给专业的DBA来做
1、定期分析表和检查表
分析表语法:ANALYZE [ LOCAL|NO_WRITE_TO_BINLOG ] TABLE tab_name
分析表的作用是可以使得系统得到准确的统计信息,使得SQL能够生成正确的执行计划
检查表语法:CHECK TABLE tab_name [option = {QUICK|FAST|MEDIUM|EXTEND|CHANGED}]
检查表的作用是检查一个或多个表是否有错误
2、定期优化表(针对MyISAM、BDB、InnoDB)
语法:OPTIMIZE[ LOCAL| NO_WRITE_TO_BINLOG ] TABLE tab_name
作用:如果已经删除了表的一大部分数据,或者如果已经对含有可变长度行的表(含有VARCHAR、BLOB或TEXT列的表)进行了很多更改,则应使用OPTIMIZE TABLE 命令来进行表优化。
这个命令可以将表中的空间碎片进行合并,并且消除由于删除或者更新造成的空间浪费。
注意:ANALYZE、CHECK、OPTIMIZE、ALTER TABLE执行期间将对表进行锁定,因此一定注意要在数据库不繁忙的时候执行相关的操作
三、常用SQL语句的优化
1、优化INSERT语句
(1)如果同时从同一客户插入很多行,应尽量使用多个值表的INSERT语句,这种方式将大大缩减客户端与数据库之间的连接、关闭等消耗,使得效率比分开执行的单个INSERT语句快
例子:insert into test value (1,2),(2,3),(3,4)...
实际mybatis案例(个人想法,毕竟大部分开发人员都是将业务逻辑放到service层)
<!--id是自定义的方法名,parameterType是List--> <insert id="insertChatHistoryList" useGeneratedKeys="true" parameterType="java.util.List"> <selectKey resultType="long" keyProperty="id"> SELECT LAST_INSERT_ID() </selectKey> insert into n_chat_history (id,from_name,to_name,message_type, content) values <!--item就是List里每一项的对象名,要用","分割每一条数据,最后要";"结尾--> <foreach collection="list" item="item" index="index" separator="," close=";"> (#{item.id,jdbcType=BIGINT}, #{item.fromName,jdbcType=VARCHAR}, #{item.toName,jdbcType=VARCHAR}, #{item.content,jdbcType=VARCHAR}, #{item.sendType,jdbcType=INTEGER}) </foreach> </insert>
在mapper.xm文件中处理列表免得多次执行insert语句,要是放到业务层的话需要多次执行insert操作
(2)如果从不同客户插入很多行,可以使用INSERT DELAYED语句得到更高的速度。DELAYED的含义是让INSERT语句马上执行,其实数据都放在内存队列中,并没有真正写入磁盘,这比每条语句分别插入要快得多;LOW_PRIORITY刚好相反,在所有其他用户对表的读写完成之后才进行插入。
(3)将索引文件和数据文件分在不同的磁盘上存放
(4)如果进行批量插入,可以通过增加bulk_insert_buffer_size变量值的方法来提高速度,但是,这只能对MyISAM表使用。
(5)当从一个文本文件装载一个表时,使用LOAD DATA INFILE。这通常比事多INSERT语句快20倍(针对MyISAM引擎)。
例如:alter table tab_name disable keys;
load data infile '/home/mysql/test.txt' inito table tab_name;
alter table tab_name enable keys;
2、优化ORDER BY 语句
mysql有两种排序方式
(1)通过有序索引顺序扫描直接返回有序数据,这种方式在使用explain分析查询时候显示Using index,不需要额外的排序,操作效率较高,例如:
(2) 第二种是通过对返回结果进行排序,也就是常说的FileSort排序,所有不是通过索引直接返回排序结果的排序都叫Filesort排序,例如
优化目标:尽量减少额外的排序,通过索引直接返回有序数据。WHERE条件和ORDER BY 使用相同的索引,并且ORDER BY的顺序和索引顺序相同,并且ORDER BY 的字段都是升序或者降序。否则肯定需要额外的排序操作,这样就会出现filesort。
总结,下列SQL可以使用索引(*为有序顺序的索引):
不适用索引(*为有序顺序的索引):
Filesort的优化:通过创建合适的索引能够减少Filesort的出现,如果条件限制下不能让filesort消失,那就加快fiesort的操作。
尽量只是用必要的字段,select具体的字段名称,而不是select* 所有的字段,这样可以减少排序区的使用,提高sql性能。
3、优化GROUP BY语句
默认情况下,Mysql对所有GROUP BY col1,col2,...的字段进行排序。这与在查询中指定ORDER BY col1,col2,...类似。因此,如果显式包括一个包含相同列的ORDER BY 子句,则对MYSQL 的实际执行性能没有影响。
如果查询包括GROUP BY 但是用户想要避免排序结果的消耗,则可以指定ORDER BY NULL 禁止排序。例如
4、优化嵌套查询(连接JOIN替代)
这个技术可以使用select 语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中,使用子查询可以一次性地完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死,并且写起来很容易。但是,有些情况下,子查询可以被更有效的连接(JOIN)替代。
例如,要从客户表中找到不在支付表payment中的所有客户信息:
explain select * from customer where customer_id not in (select customer_id from payment);
如果使用连接(JOIN)来完成这个查询工作,速度将会快很多。尤其是当payment表中对customer_id建有索引,性能将会更好,具体语句如下:
explain select * from customer a left join payment b on a.customer_id = b.customer_id where b.customer_id is null;
连接(JOIN)之所以更有效率一些,是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作
5、优化OR条件
对于含有OR的查询子句,如果要利用 索引,则OR之间的每个条件列都必须用到索引;如果没有索引,则应考虑增加索引。
现有三个索引:在id和year两个字段上分别有1个独立的索引,在company_id和year字段上有1个复合索引。
(1)where id =3 or year =2018;
这是可以正确用到索引的。
(2)where company_id=3 or year= 2018;
这是无法用到索引的。
6、优化分页查询
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。一个常见又非常头痛的分页场景是“limit 1000,20”,此时mysql排序出前1020条记录后急需要返回第1001到1020条记录,前1000条记录都会被抛弃,查询和排序的代价非常高。
优化思路:在索引上完成排序分页的操作,最够根据主键关联回原表查询锁需要的其他列内容。例如:对电影表 film根据title排序后取某一页数据,直接查询的时候,能够从explain的输出结果中看到优化器实际上做了全表扫描,处理效果并不高:
explain select film_id,desc from film order by title limit 50,5;
优化后sql语句
explain select a.film_id ,a.decs from film a inner join (select film_id from film order by title limit 50,5)b on a.film_id = b.film_id;
这种方式可以是mysql扫描尽可能少的页面来提高分页效率。
7、使用SQL提示
SQL提示是优化数据库的一个重要手段,简单来说就是人为 在SQL语句中加入提示来达到优化操作的目的
(1)use index(推荐选中索引,忽略其他索引)
在查询语句中表名的后面,添加use index 来提供希望mysql去参考的索引列表,这样mysql就不会考虑其他可用的索引。
select count(*) from rental use index (id_rental_date);
(2)ignore index (忽略其他索引)
如果用户只是单纯地想让mysql忽略 一个或者多个索引,则可以使用ignore index 。
select count(*) from rental ignore index (idx_rental_date);
(3)force index(强制使用选中索引)
为强制使用一个特定的索引,可在查询中使用force index ,使用了这个即使使用索引效率不高,mysql还是选择使用索引
select count(*) from rental force index (idx_fk_inventory_id ) where inventory_id >1 ;
四、其他优化
1、优化SQL语句,查询语句中尽量不使用select *,用哪个字段查哪个字段;
2、少用子查询可用表连接代替;
3、少用模糊查询;
4、数据表中创建索引;
5、对程序中经常用到的数据生成缓存;
6、Where子句中:where表之间的连接必须写在其他Where条件之前,那些可以过滤掉最大数量记录的条件必须写在Where子句的末尾.HAVING最后。
7、用EXISTS替代IN、用NOT EXISTS替代NOT IN。
8、 避免在索引列上使用计算
9、避免在索引列上使用IS NULL和IS NOT NULL
10、对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
11、应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
12、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描