有时候咱们会遇到:在查询sql的时候,假若有100w条数据,会出现慢sql告警,这个时候你就应该处处sql日志来查找缘由了。这里颇有可能的主要缘由就是没有命中索引和没有分页处理(缘由有不少种,主要分析你的日志)。那接下来咱们就得去优化sql了。如何优化呢?下面咱们来谈谈有关的问题。mysql
谈到sql性能优化,那咱们就离不开谈到大数据量和并发数,MySQL没有限制单表的最大记录数,它只是取决于操做系统对文件大小的限制。看表:算法
从表中咱们能够看出,若是单表行数超过500万行或者单表容量超过2GB,才推荐分库分表。性能由综合因素决定,抛开业务复杂度,影响程度依次是硬件配置、MySQL配置、数据表设计、索引优化。500万这个值仅供参考,并不是铁律。sql
假如你在操做过超过4亿行数据的单表,你能够用分页查询,分页查询最新的20条记录耗时0.6秒,SQL语句大体是select field_1,field_2 from table where id < #{prePageMinId} order by id desc limit 20,prePageMinId是上一页数据记录的最小ID。数据库
这个查询出来的查询速度还凑合,不过随着数据不断增加,有朝一日一定不堪重负。因此分库分表是个周期长而风险高的大活儿,应该尽量在当前结构上优化,好比升级硬件、迁移历史数据等等,实在没辙了再分。性能优化
最大并发数bash
并发数是指同一时刻数据库能处理多少个请求,由max_connections和max_user_connections决定。max_connections是指MySQL实例的最大链接数,上限值是16384,max_user_connections是指每一个数据库用户的最大链接数。
MySQL会为每一个链接提供缓冲区,意味着消耗更多的内存。若是链接数设置过高硬件吃不消,过低又不能充分利用硬件。通常要求二者比值超过10%,计算方法以下:网络
max_used_connections / max_connections * 100% = 3/100 *100% ≈ 3%
复制代码
查看最大链接数与响应最大链接数以下:并发
show variables like '%max_connections%';
show variables like '%max_user_connections%';
复制代码
在配置文件my.cnf中你能够修改最大链接数数据库设计
[mysqld]
max_connections = 100
max_used_connections = 20
复制代码
查询耗时0.5秒函数
建议将单次查询耗时控制在0.5秒之内,0.5秒是个经验值,源于用户体验的3秒原则。若是用户的操做3秒内没有响应,将会厌烦甚至退出。响应时间=客户端UI渲染耗时+网络请求耗时+应用程序处理耗时+查询数据库耗时,0.5秒就是留给数据库1/6的处理时间。
相比datetime,timestamp占用更少的空间,以UTC的格式储存自动转换时区。
避免空值
MySQL中字段为NULL时依然占用空间,会使索引、索引统计更加复杂。从NULL值更新到非NULL没法作到原地更新,容易发生索引分裂影响性能。尽量将NULL值用有意义的值代替,也能避免SQL语句里面包含is not null的判断。
text类型优化
因为text字段储存大量数据,表容量会很早涨上去,影响其余字段的查询性能。建议抽取出来放在子表里,用业务主键关联。
索引的分类有哪些?
1 普通索引:最基本的索引
2 组合索引:多个字段上创建的索引,可以加速复合查询条件的检索。
3 惟一索引:与普通索引相似,但索引列的值必须惟一,容许有空值
4 组合惟一索引:列值的组合必须惟一
5 主键索引:特殊的惟一索引,用于惟一标识数据表中的某一条记录,不容许有空值,通常用primary key约束。
6 全文索引:用于海量文本的查询,MySQL5.6以后的InnoDB和MyISAM均支持全文索引。因为查询精度以及扩展性不佳,更多的企业选择Elasticsearch。
索引优化
1 分页查询很重要,若是查询数据量超过30%,MYSQL不会使用索引。
2 单表索引数不超过5个、单个索引字段数不超过5个。
3 字符串可以使用前缀索引,前缀长度控制在5-8个字符。
4 字段惟一性过低,增长索引没有意义,如:是否删除、性别。
合理使用覆盖索引,以下所示:
select login_name, nick_name from member where login_name = ?复制代码
update status=0 FROM `coupon` WHERE expire_date <= #{currentDate} and status=1;
复制代码
若是大量优惠券须要更新为不可用状态,执行这条SQL可能会堵死其余SQL,分批处理伪代码以下:
int pageNo = 1;
int PAGE_SIZE = 100;
while(true) {
List<Integer> batchIdList = queryList('select id FROM `coupon` WHERE expire_date <= #{currentDate} and status = 1 limit #{(pageNo-1) * PAGE_SIZE},#{PAGE_SIZE}');
if (CollectionUtils.isEmpty(batchIdList)) {
return;
}
update('update status = 0 FROM `coupon` where status = 1 and id in #{batchIdList}')
pageNo ++;
}
复制代码
操做符<>优化
一般<>操做符没法使用索引,举例以下,查询金额不为100元的订单:
select id from orders where amount != 100;
复制代码
若是金额为100的订单极少,这种数据分布严重不均的状况下,有可能使用索引。鉴于这种不肯定性,采用union聚合搜索结果,改写方法以下:
(select id from orders where amount > 100)
union all
(select id from orders where amount < 100 and amount > 0)
复制代码
OR优化
在Innodb引擎下or没法使用组合索引,好比:
select id,product_name from orders where mobile_no = '13421800407' or user_id = 100;
复制代码
OR没法命中mobile_no + user_id的组合索引,可采用union,以下所示:
(select id,product_name from orders where mobile_no = '13421800407')
union
(select id,product_name from orders where user_id = 100);
复制代码
此时id和product_name字段都有索引,查询才最高效。
IN优化
IN适合主表大子表小,EXIST适合主表小子表大。因为查询优化器的不断升级,不少场景这二者性能差很少同样了。
尝试改成join查询,举例以下:
select id from orders where user_id in (select id from user where level = 'VIP');复制代码
采用JOIN以下所示:
select o.id from orders o left join user u on o.user_id = u.id where u.level = 'VIP';复制代码
不作列运算
一般在查询条件列运算会致使索引失效,以下所示:
查询当日订单
select id from order where date_format(create_time,'%Y-%m-%d') = '2019-07-01';复制代码
date_format函数会致使这个查询没法使用索引,改写后:
select id from order where create_time between '2019-07-01 00:00:00'
and '2019-07-01 23:59:59';
复制代码
避免Select all
若是不查询表中全部的列,避免使用SELECT *,它会进行全表扫描,不能有效利用索引。
Like优化
like用于模糊查询,举个例子(field已创建索引):
SELECT column FROM table WHERE field like '%keyword%';
复制代码
这个查询未命中索引,换成下面的写法:
SELECT column FROM table WHERE field like 'keyword%';
复制代码
去除了前面的%查询将会命中索引,可是产品经理必定要先后模糊匹配呢?全文索引fulltext能够尝试一下,但Elasticsearch才是终极武器。
Join优化
join的实现是采用Nested Loop Join算法,就是经过驱动表的结果集做为基础数据,经过该结数据做为过滤条件到下一个表中循环查询数据,而后合并结果。若是有多个join,则将前面的结果集做为循环数据,再次到后一个表中查询数据。
驱动表和被驱动表尽量增长查询条件,知足ON的条件而少用Where,用小结果集驱动大结果集。
被驱动表的join字段上加上索引,没法创建索引的时候,设置足够的Join Buffer Size。
禁止join链接三个以上的表,尝试增长冗余字段。
Limit优化
limit用于分页查询时越日后翻性能越差,解决的原则:缩小扫描范围,以下所示:
select * from orders order by id desc limit 100000,10
复制代码
select * from orders order by id desc limit 1000000,10
复制代码
select * from orders where id > (select id from orders order by id desc
limit 1000000, 1) order by id desc limit 0,10
复制代码
select id from orders where id between 1000000 and 1000010 order by id desc复制代码
若是以上方案依然很慢呢?只好用游标了。