前言
之前看到一朋友发我一代码,让我看看他们公司前同事"高级"的优化方案,号称百万级优化代码!
此代码一出,整个人如醍醐灌顶,仿佛打开了新世界的大门!
当然,今天说的肯定不是这种级别的调优(技术有限,达不到),今天来讲讲,大部分后端会遇到的Mysql调优。
SQL调优的目的
一次典型的调优过程,需要达到以下的其中一个目的:
- 减少用户响应时间,即从用户发出命令到得到响应这一段时间
- 提高吞吐,即让一条SQL只使用最少的资源 对于一个响应时间问题,你可以参考,一个在线售书应用的用户在更新购物车的时候卡住了三分钟。 相对的,吞吐问题,则想象在一个数据仓库中,一条并行运行三分钟的语句消耗了整个DB服务器的CPU资源,导致其它查询无法运行。
SQL 语句生命周期
- mysql 请求分为两种
- 一种是需要命令解析和分发才能执行
- 另一种可以直接执行;不管哪种,如果开启了日志,那么日志模块会记录日志;
-
如果是 Query 类型的请求,会将控制权交给 Query 解析器,Query 解析器检查是否 Select 类型,如果是则启动查询缓存模块,如果缓存命中则将缓存数据返回给连接线程模块,连接线程将数据传递到客户端;如果没有缓存或者不是一个可以缓存的查询,此时解析器会进行相应的处理,通过查询分发器给相关的处理模块;
-
如果解析器结果是 DML/DDL,则交给变更模块;如果是检查、修复的查询交给表维护模块,如果是一条没有被缓存的语句,则交给查询优化器模块。实际上表变更模块又分为若干小模块,
- 例如:insert 处理器、delete 处理器、update 处理器、create 处理器,以及 alter处理器这些小模块来负责不同的 DML 和 DDL。
- 总之,查询优化器、表变更模块、表维护模块、复制模块、状态模块都是根据命令解析器的结果不同而分发给不同的类型模块,最后和存储引擎进行交互读取当前 SQL 语句满足条件的数据。
- 当一条命令执行完毕后,控制权都会还给连接线程模块,在上面各个模块处理过程中都依赖于核心 API 模块,比如:内存管理、文件 I/O,字符串处理
1.SQL 语句优化
1.应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
3.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
4.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
5.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'
--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0
--'2005-11-30'生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
6.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将不能正确使用索引。 11.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
7.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
8.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
9.order by使用
注意:Mysql5.7及以上版本group by 子查询中order by 无效
要通过limit 语句使子查询的order by生效,但是这必须保证limit的数量,所以也可以使用DISTINCT实现。