SQL优化的一般步骤
通过show status命令了解各种SQL的执行频率
定位执行效率较低的SQL语句,重点select
通过explain分析低效率的SQL
确定问题并采取相应的优化措施
优化措施
show 参数
MySQL客户端连接成功后,通过使用show [session|global] status 命令可以提供服务器状态信息。其中的session来表示当前的连接的统计结果,global来表示自数据库上次启动至今的统计结果。默认是session级别的。
show status like 'Com_%';
show global status like 'Com_%';
下面的例子:
其中Com_XXX表示XXX语句所执行的次数。
-- 查询本次会话
show status like 'Com_XXX'; -- 等效于 show session status like 'Com_XXX'; -- 举例 show session status like 'Com_select';
-- 查询全局
show global status like 'Com_XXX'; -- 举例 show global status like 'Com_select';
重点注意:Com_select,Com_insert,Com_update,Com_delete通过这几个参数,可以容易地了解到当前数据库的应用是以插入更新为主还是以查询操作为主,以及各类的SQL大致的执行比例是多少。
还有几个常用的参数便于用户了解数据库的基本情况。
Connections:试图连接MySQL服务器的次数
Uptime:服务器工作的时间(单位秒)
Slow_queries:慢查询的次数 (默认是慢查询时间10s)
show status like 'Connections'; show status like 'Uptime'; show status like 'Slow_queries';
定位慢查询
可通过开启慢查询日志来找出较慢的SQL
如何查询MySQL的慢查询时间
show variables like 'long_query_time'; -- 未修改的情况下是10
修改MySQL慢查询时间
set long_query_time=1;
默认情况下,MySQL认为10秒才是一个慢查询,这里为了测试,将1秒设置为一个慢查询
set long_query_time=1
这时我们如果出现一条语句执行时间超过1秒时,就会统计到到我们的一个日志中
my.ini的所在目录为:C:ProgramDataMySQLMySQL Server 版本号my.ini
该慢查询日志会放在Data目录下,可需要查看 my.ini 的 datadir=""确定
具体文件名可查看 my.ini 的slow_query_log_file对应的值,低版本的mysql需要通过在开启mysql时使用--log-slow-queries=file_name来配置
在默认情况下,低版本的MySQL不会记录慢查询,需要在启动MySQL时候,指定记录慢查询才可以
binmysqld.exe --log-slow-queries=D:/mysql.log
[低版本mysql5.0可以在my.ini指定]
针对 mysql5.5启动慢查询有两种方法
binmysqld.exe --safe-mode --slow-query-log
[mysql5.5 可以在my.ini指定]
也可以在my.ini 文件中配置:
[mysqld] # The TCP/IP Port the MySQL Server will listen on port=3306 slow-query-log
而在mysql5.6及以上中,默认是启动记录慢查询的,,其中有一个配置项
slow-query-log=1
这时我们如果出现一条语句执行时间超过1秒中,就会统计到日志中
-- 查询哪个部门员工最多,我这边最多的是产品部,所以我用产品部数据进行测试 select d.name, e.count_no_inner count_no_outer from (select dept_no, count(no) count_no_inner from emp group by dept_no) e left join dept d on e.dept_no = d.no order by count_no_outer desc; UPDATE emp SET dept_no = 1 WHERE no = 100002; -- 25.906sec SELECT * FROM emp e WHERE dept_no in (SELECT no FROM dept WHERE name='产品部'); -- 秒查 -- 如果最带上ORDER BY e.no,速度就会更慢 -- 本地测试,6.094sec左右 SELECT * FROM emp e WHERE dept_no in (SELECT no FROM dept WHERE name='产品部') ORDER BY e.no; -- 优化,1.547sec SELECT * FROM emp e WHERE dept_no = (SELECT no FROM dept WHERE name='产品部') ORDER BY e.no;
explain分析SQL
-- 造数据 update emp set name = 'Jefabc' where no = '100001';
参考 EXPLAIN详解
语句优化
在查询中不要使用select * 获取列数据【返回更少的数据】
讲解:
1、检索不必要的列会带来额外的系统开销,该省则省,特别是LOB类型的列
优点:
1、减少数据在网络上传输开销
2、减少服务器数据处理开销
3、减少客户端内存占用
4、字段变更时提前发现问题,减少程序BUG
5、如果访问的所有字段刚好在一个索引里面,则可以使用纯索引访问提高性能。
缺点:增加编码工作量
由于会增加一些编码工作量,所以一般需求通过开发规范来要求程序员这么做,否则等项目上线后再整改工作量更大。
如果你的查询表中有大字段或内容较多的字段,如备注信息、文件内容等等,那在查询表时一定要注意这方面的问题,否则可能会带来严重的性能问题。如果表经常要查询并且请求大内容字段的概率很低,我们可以采用分表处理,将一个大表分拆成两个一对一的关系表,将不常用的大内容字段放在一张单独的表中。如一张存储上传文件的表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE,FILE_CONTENT)
我们可以分拆成两张一对一的关系表:
T_FILE(ID,FILE_NAME,FILE_SIZE,FILE_TYPE)
T_FILECONTENT(ID, FILE_CONTENT)
通过这种分拆,可以大大提少T_FILE表的单条记录及总大小,这样在查询T_FILE时性能会更好,当需要查询FILE_CONTENT字段内容时再访问T_FILECONTENT表。
在select字段中避免不必要的列,连接条件中避免不必要的表【返回更少的数据】
1、如一中的讲解
2、连接条件中包含不必要的表会强制数据库引擎检索和匹配不需要的数据,增加了查询执行时间
不要在子查询中使用count()求和执行存在性检查
1、不要使用
select column_list from table where 0 < (select count(*) from table2 where ...)
2、使用下面的语句代替
sleect column_list from table where exists (select column_list from table2 where ...)
讲解:
使用count()时,数据库不知道你是在做存在性检查,它会计算所有匹配的值,要么会执行全表扫描,要么会扫描最小的非聚集索引;
而使用exists时,数据库就知道你此时是在做存在性检查了,当它发现第一个匹配的值时,就会返回true,并停止查询。类似的,可以使用in或者any代替count()。
使用全文搜索搜索文本数据,取代like搜索
全文搜索始终优于like搜索:
1、全文搜索让你可以实现like不能完成的复杂搜索,如搜索一个单词或一个短语,搜索一个与另一个单词或短语相近的单词或短语,或者是搜索同义词;
2、实现全文搜索比实现like搜索更容易(特别是复杂的搜索);
使用union实现or操作
1、在查询中尽量不要使用or,使用union合并两个不同的查询结果集,这样查询性能会更好;
2、如果不是必须要不同的结果集,使用union all效果会更好,因为它不会对结果集排序。
为大对象使用延迟加载策略
1、在不同的表中存储大对象(如VARCHAR(MAX),Image,Text等),然后在主表中存储这些大对象的引用;
2、在查询中检索所有主表数据,如果需要载入大对象,按需从大对象表中检索大对象。
使用 join 优化子查询
参考JOIN从句
-- 复杂语句
select a.name, (select b.name from test_b b where a.name = b.name) b_name from test_a a;
-- join 优化
select a.name, b.name b_name from test_a a left join test_b b on a.name = b.name;
other:
因为使用join,MySQL不需要在内存中创建临时表
优化group by 语句
默认情况,MySQL对所有的group by col1,col2进行排序。这与在查询中指定order by col1, col2类似。如果查询中包括group by但用户想要避免排序结果的消耗,则可以使用order by null禁止排序
- 有索引的字段尽量走有索引的字段
- 如果想要在含有or的查询语句中利用索引,则or之间的每个条件列都必须用到索引,如果没有索引,则应该考虑增加索引
- 关联查询多用where条件刷选
- 少用in,not in,多用exists,not exists,=
- 少用<>,用> <
- 能在代码里完成的业务逻辑就尽量不要放在SQL里
- 尽量让SQL中少出现case、when、then,这样会让逻辑更清楚,让SQL可读性更强
- 文件、图片等大文件用文件系统存储,不用数据库
- 不用多说,铁律!!!数据库只存储路径。
- 集中批量查询【减少数据库交互次数】
- 不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
- sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
- OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
- 不用函数和触发器,在应用程序实现
- 避免%xxx式查询
- 少用JOIN
- 使用同类型进行比较,比如用'123'和'123'比,123和123比
- 尽量避免在WHERE子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
- 对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
- 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大
tips:新增、删除、修改操作可参考查询操作