SQL优化
查看SQL执行频率
show status 命令可以查看服务器状态信息
show [session|global] status
定位低效率SQL
可以通过两种方式定位执行效率较低的SQL语句:
慢查询日志
通过慢查询日志定位那些执行效率较低的SQL语句,用--log-slow-queries[=file_name]选项启动时,mysqld写一个包含所有执行时间超过long_query_time秒的SQL语句的日志文件。
show processlist
慢查询日志在查询结束之后才记录,所以在应用反映执行效率出现问题的时候查询慢查询日志并不能定位问题。可以使用show processlist命令查看当前MySQL在进行的线程,包括线程的状态、是否锁表等,可以实时地查看SQL的执行情况,同时对一些锁表操作进行优化
explain分析执行计划
explain或者desc命令获取MySQL如何执行select语句的信息,包括select语句执行过程中表如何连接和连接的顺序等。
id
id字段是select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序
- id相同:表示加载表的顺序是从上到下
- id不同:id值越大优先级越高
- id有相同也有不同:id相同的可以认为是一组从上往下顺序执行;所有组中id值越大优先级越高
select_type
- SIMPLE:简单的select查询,查询中不包含子查询或者UNION
- PRIMARY:查询中若包含任何复杂的子查询,最外层查询标记为该标识
- SUBQUERY:在select或者where列表中包含了子查询
- DERIVED:在from列表中包含的子查询被标记为DERIVED(衍生)MYSQL会递归执行这些子查询,把结果放在临时表中
- UNION:若第二个SELECT出现在UNION之后则标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为DERIVED
- UNION RESULT:从UNION表获取结果的SELECT
type
- NULL:MySQL不访问任何表、索引,直接返回结果
- system:表只有一行记录(等于系统表),这是const类型的特例,一般不会出现
- const:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快
- eq_ref:类似ref,区别在于使用的是唯一索引,使用主键的关联查询,关联查询出的记录只有一条,常见于主键或者唯一索引扫描
- ref:非唯一索引扫描,返回匹配某个单独值的所在行。本质上也是一种索引访问,返回所有匹配某个单独值的所有行
- range:只检索给定返回的行,使用一个索引来选择行。where之后出现between、<、>、in等操作
- index:index与all的区别为index类型只是遍历了索引树,通常比ALL快,ALL是遍历数据文件
- all:将遍历全表以找到匹配的行
extra
- using filesort:说明mysql会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,称为文件排序
- using temporary:使用临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于order by和group by
- using index:表示相应的select操作使用了覆盖索引,避免访问表的数据行,效率不错
show profile分析SQL
MySQL从5.0.37版本开始增加对show profile和show profiles语句的支持。
通过have_profiling参数能够看到当前mysql是否支持profile
mysql> select @@have_profiling;
默认profiling是关闭的,可以通过set语句在session级别开启profiling
mysql> select @@profiling;
mysql> set profiling = 1;
查看SQL语句耗时:
mysql> show profiles;
mysql> show profile for query query_id -- 查看到该SQL执行过程中每个线程的状态和消耗的时间
TIP:
Sending Data状态表示MySQL线程开始访问数据行并把结果返回给客户端,而不仅仅是返回给客户端。由于在Sending data状态下,MySQL线程往往需要做大量的磁盘读取操作,所以经常是整个查询中耗时最长的状态。
Trace分析优化器执行计划
MySQL5.6提供了对SQL的跟踪trace,通过trace文件能够进一步了解为什么优化器选择A计划而不是B计划
开启trace
mysql> set optimizer_trace="enabled=on", end_markers_in_json=on;
mysql> set optimizer_trace_max_mem_size=1000000;
检查information_schema.optimizer_trace
mysql> select * from information_schema.optimizer_trace\G;
索引失效情况
- 范围查询右边的列不能使用索引
- 不要在索引列上进行运算操作,索引将失效
- 字符串不加单引号造成索引失效(mysql进行了隐式转换)
- 用or分割开的条件,如果or之前的列有索引,or之后的列没有索引,那么涉及到的索引都不会被用到
- 以%开头的like模糊查询,索引失效【通过覆盖索引能解决此问题】
- 如果MySQL评估使用索引比全表更慢,则不使用索引【比如status 99%都为1】
- is null, is not null 有时索引失效【绝大部分数据是null,is null会走索引。绝大部分数据不为null,is not null走索引】
- in走索引,not in不走索引
- 尽量使用复合索引,而少使用单列索引
优化方法
1、大批量导入数据
使用load命令导入数据的时候,适当的设置可以提高导入的效率
- 主键顺序插入
因为InnoDB类型的表是按照主键的顺序保存的,所以导入的数据按照主键的顺序排列可以有效的提高导入数据的效率。如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,也可以提高导入效率 - 关闭唯一性校验
在导入数据前执行SET UNIQUE_CHECKS=0关闭唯一性校验,导入结束后执行SET UNIQUE_CHECKS=1恢复唯一性校验可以提高导入的效率 - 手动提交事务
如果应用使用自动提交的方式,建议在导入前执行SET AUTOCOMMIT=0关闭自动提交,导入结束后再执行SET AUTOCOMMIT=1打开自动提交
2、Insert
- 如果需要同时对一张表插入很多行数据时,应该尽量使用多个值表的insert语句,这种方式将大大的缩减客户端与数据库之间的连接、关闭等消耗
insert into tb_test values(1, "tom");
insert into tb_test values(2, "cat");
insert into tb_test values(3, "jerry");
改为
insert into tb_test values(1, "tom"), (2, "cat"), (3, "jerry");
- 事务提交改为手动提交
start transaction;
insert into tb_test values(1, "tom");
insert into tb_test values(2, "cat");
insert into tb_test values(3, "jerry");
commit;
- 顺序插入
3、order by
create index idx_emp_age_salary on emp(age, salary);
两种排序
- 通过对返回数据进行排序,就是常说的filesort排序,所有不是通过索引直接返回排序结果的排序都是filesort排序
select * from emp order by age desc;
select * from emp order by age asc;
select * from emp order by age, salary;
select id, age, salary from emp order by age desc, salary asc;
select id, age, salary from emp order by salary, age;
- 通过有序索引扫描直接返回有序数据,这种情况称为using index,不需要额外排序,操作效率高
select id, age, salary from emp order by age, salary;
select id, age, salary from emp order by age desc, salary desc;
tips: 尽量减少额外的排序,通过索引直接返回有序数据。where条件和order by使用相同的索引,并且order by的顺序和索引顺序相同,并且order by的字段都是升序或者降序。否则肯定需要额外的排序。
Filesort的优化
- 两次扫描优化
MySQL4.1之前使用该方式排序。首先根据条件取出排序字段和行指针信息,然后在排序区sort buffer中排序,如果sort buffer不够,则在临时表temporary table中存储排序结果。完成排序后,再根据行指针回表读取记录,该操作可能会导致大量随机I/O操作 - 一次扫描
一次性取出满足条件的所有字段,然后在排序区sort buffer中排序后直接输出结果集。排序时内存开销较大,但是排序效率比两次扫描算法高
mysql通过比较系统变量 max_length_for_sort_data的大小和Query语句取出的字段总大小来判断使用哪种排序算法,如果max_length_for_sort_data更大,采用第二种算法,否则采用第一种算法
可以适当提高sort_buffer_size 和 max_length_for_sort_data系统变量,来增大排序区的大小
mysql> show variables like 'max_length_for_sort_data';
mysql> show variables like 'sort_buffer_size';
4、group by
group by实际上也会进行排序操作,而且与order by相比,group by只是多了排序之后的分组操作。
如果查询包含group by但是用户想要避免排序结果的消耗,可以执行order by null禁止排序
select age, count(*) from emp group by age;
优化后
select age, count(*) from emp group by age order by null;
5、优化嵌套查询
使用多表联查代替子查询
select * from t_user where id in (select user_id from user_role);
优化后
select * from t_user u, user_role ur where u.id = ur.user_id;
6、or
使用UNION替换or
select * from t_user where id = 1 or age = 10;
优化后
select * from t_user where id = 1
union
select * from t_user where age = 10;
7、分页
一般分页查询时通过创建覆盖索引能够比较好的提高性能。一个常见又非常头痛的问题是 limit 2000000, 10,此时需要MySQL排序前2000010记录,仅返回2000000 - 2000010的记录,查询代价非常大
- 在索引上完成排序分页操作,最后根据主键关联回原表查询所需要的其他列内容
select * from t_user u, (select id from t_user order by id limit 2000000, 10) a where u.id = a.id;
- 可以把limit查询转换为某个位置的查询,适用于主键自增的表
select * from t_user where id > 2000000 order by id limit 10;
SQL提示
SQL提示是优化数据库的一个重要手段,简单说就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
USE INDEX
在查询语句中表名的后面添加use index来提供希望MySQL去参考的索引列表,这样就可以让MySQL不再考虑其他可用的索引。
select * from emp use index(name) where name = 'zs';
IGNORE INDEX
如果只是单纯的想忽略一个或多个索引,可以使用ignore index作为hint
select * from emp ignore index(name) where name = 'zs';
FORCE INDEX
强制MySQL使用一个特定的索引,可在查询中使用force index作为hint
select * from emp force index(name) where name = 'zs';
TIPS: use index 和 force index 的区别在于 use index 只是让MySQL去参考,最终不一定会采用;而 force index 是一定会走索引。
数据库优化
查询缓存优化
开启MySQL的查询缓存,当执行完全相同的SQL语句的时候,服务器就会直接从缓存中读取结果,当数据被修改,之前的缓存会失效,修改比较频繁的表不适合做查询缓存。
操作流程
-
- 客户端发送一条查询给服务器
- 服务器先检查查询缓存,如果命中缓存,则立即返回存储在缓存中的结果,否则进入下一阶段
- 服务器进行SQL解析、预处理,再由优化器生成对应的执行计划
- 根据优化器生成的执行计划调用存储引擎的API来执行查询
- 将结果返回给客户端,并将结果同时缓存起来
查询缓存配置
- 查看当前的MySQL数据库是否支持查询缓存
mysql>show variables like 'have_query_cache';
- 查看当前MySQL是否开启了查询缓存
mysql>show variables like 'query_cache_type';
- 查看查询缓存的占用大小
mysql>show variables like 'query_cache_size';
- 查看查询缓存的状态变量
mysql>show status like 'Qcache%';
开启查询缓存
query_cache_type的取值:
- OFF(0):功能关闭
- ON(1):功能打开,SELECT的结果符合缓存条件即会缓存,否则不予缓存;显示指定SQL_NO_CACHE不予缓存
- DEMAND(2):功能按需进行,显示指定SQL_CACHE的select语句才会缓存;其他不予缓存
在 /usr/my.cnf配置中增加
#开启mysql的查询缓存
query_cache_type=1
查询缓存SELECT选项
可以在select语句中指定两个与查询缓存相关的选项
- SQL_CACHE:如果查询结果时可缓存的,并且query_cache_type值为ON或DEMAND,则缓存查询结果
- SQL_NO_CACHE:不使用查询缓存。既不检测查询缓存,也不检查结果是否已缓存,也不缓存查询结果
select SQL_CACHE id, name from emp;
select SQL_NO_CACHE id, name from emp;
查询缓存失效情况
- SQL语句不一致的情况:想要命中查询缓存,查询的SQL必须一致
- 当查询语句中有一些不确定时,则不会缓存。例如 now(), current_date(0, curdate(), curtime(), rand(), uuid(), user(), database()等
- 不使用任何表查询语句
- 查询mysql,information_schema,performance_schema数据库中的表时,不会走查询索引
- 在存储的函数,触发器或事件的主体内执行的查询
- 如果表更改,则使用该表的所有高速缓存查询都将变为无效并从高速缓存中删除。如:insert, update, delete, truncate table, alter table, drop table, drop database。
内存优化
优化原则
- 将尽量多的内存分配给MySQL做缓存,但要给操作系统和其他程序预留足够内存
- MyISAM存储引擎的数据文件读取依赖于操作系统自身的IO缓存,因此如果有MyISAM表,就要预留更多的内存给操作系统做IO缓存
- 排序区、连接区等缓存是分配给每个数据库会话(session)专用的,其默认的设置要根据最大连接数合理分配,如果设置过大,不但浪费空间,而且在并发连接较高时会导致物理内存耗尽。
MyISAM内存优化
myisam存储引擎使用 key_buffer 缓存索引块,加速myisam索引的读写速度。对于myisam表的数据块,mysql没有特别的缓存机制,完全依赖于操作系统的IO缓存。
key_buffer_size
key_buffer_size 决定MyISAM索引块缓存区的大小,直接影响到MyISAM表的存取效率。
*对于一般MyISAM数据库,建议至少将1/4可用内存空间分配给 key_buffer_size
在 /usr/my.cnf 中做如下配置:
key_buffer_size=512M
read_buffer_size
如果需要经常顺序扫描myisam表,可以通过增大read_buffer_size的值来改善性能。
read_rnd_buffer_size
对于需要做排序的mysiam表的查询,如带有order by子句的sql,可以适当增加 read_rnd_buffer_size 的值来改善性能。
TIPS: read_buffer_size 和 read_rnd_buffer_size 都是每个session独占的,默认值设置太大会造成内存浪费。
InnoDB内存优化
innodb用一块内存区域做IO缓存池,该缓存池不仅用来缓存innodb索引块,而且也用来缓存数据块。
innodb_buffer_pool_size
变量决定了innodb存储引擎表数据和索引数据的最大缓存区大小。在保证操作系统及其他程序有足够内存可用的情况下,innodb_buffer_pool_size的值越大越好,缓存命中率越高,访问innodb表需要的磁盘I/O就越少,性能也就越高。
innodb_buffer_pool_size=512M
innodb_log_buffer_size
变量决定了innodb重做日志缓存的大小,对于可能产生大量更新记录的大事务,增加 innodb_log_buffer_size 的大小,可以避免innodb在事务提交前就执行不必要的日志写入磁盘操作。
innodb_log_buffer_size=10M
并发参数
max_connections
max_connections控制允许连接到MySQL数据库的最大数量,默认值是151.如果状态变量 connection_errors_max_connections 不为零,并且一直增长,则说明不断有连接请求因数据库连接数已达到最大值而失败,这时可以考虑增大 max_connections 的值。
MySQL最大可支持的连接数取决于很多因素,包括给定操作系统平台的线程库的质量、内存大小、每个连接的负荷,CPU的处理速度,期望的相应时间等。在linux平台下,性能好的服务器,支持500~1000个连接不是难事,需要根据服务器性能进行评估设定。
back_log
back_log 参数控制MySQL监听TCP端口时设置的积压请求栈大小。如果MySQL的连接数达到 max_connections 时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即 back_log,如果等待连接的数量超过 back_log,将不被授予连接资源,将会报错。5.6.6版本之前默认值为50,之后的版本默认为50 + ( max_connections / 5 ),但不超过900。
如果需要数据库在较短时间内处理大量连接请求,可以考虑适当增大back_log的值
table_open_cache
table_open_cache参数用来控制所有SQL语句执行线程可打开表缓存的数量,而在执行SQL语句时,每一个SQL执行线程至少要打开1个表缓存。该参数的值应该根据设置的最大连接数 max_connections 以及每个连接执行关联查询中涉及的表的最大数量来设定:max_connections * N
thread_cache_size
为了加快连接数据库的速度,MySQL会缓存一定数量的客户服务线程以备重用,通过参数 thread_cache_size 可控制MySQL缓存客户服务线程的数量。
innodb_lock_wait_timeout
用来设置innodb事务等待行锁的时间,默认值是50ms,可以根据需要进行动态设置。对于需要快速反馈的业务系统来说,可以将行锁的等待时间调小,以避免事务长时间挂起;对于后台运行的批处理程序来说,可以将行锁的等待时间调大,以避免发生大的回滚操作。