SQL优化(慢查询优化方法)正确使用数据库索引

(一) 建立索引的正确姿势

1 )索引不要包含选择性过低字段

选择性过低,即通过该字段只能过滤掉少部分的数据,是没必要建立索引的,因为如果该数据只是占小部分,即使没有索引直接查询数据表也不用过多的遍历即可找到目标数据,没有必要基于索引查询。

2) 选择性高的字段前置或者单独建立索引

原因是组合索引底层的存储先按照第一个进行排序,第一个字段相同再按照第二字段排序,如果选择性低的字段放在前面,因此选择性高的字段放前面相对而言IO的次数可能会减少一些。

3)尽量使用覆盖索引

如果col_a和col_b过滤完后还有大量数据,那么建议建一个index(col_a,col_b,col_c)索引,否则MySQL需要通过大量回表去查询col_c的数据再去求和。

(二) 使用索引的正确姿势

1) 最左匹配截断

索引:
index(col_a,col_b)

组合索引的匹配规则是从左往右匹配,无论是作为过滤条件还是排序条件都要遵循这个原则。如果要使用col_b字段走索引,查询条件则必须要携带col_a字段

2) 隐式转换

字段类型和查询数据的值类型不一致,会导致字段上的索引失效。

3) in+ order by 导致排序失效

索引:
index(col_a,col_b)

SQL:

  
select * from my_table where col_a in (1,2) order by col_b
  • 如果col_a的过滤性不高,在组合索引中可以通过将col_b字段前置,将col_a移动到组合索引后面,只用于避免或减少回表。
  • 如果col_a的过滤性高,过滤后的数据相对较少,则维持当前的索引即可,剩余不多的数据通过filesort进行排序。
  • 如果存在大量数据,并且经过col_b过滤后还是存在大量数据,建议基于别的数据存储实现,比如Elasticsearch。

4) or查询导致失效

索引:
index(col_a,col_b)
SQL:

select * from table where col_a=1 or col_b=''

or查询会导致索引失效,可以将col_a和col_b分别建立索引,利用Mysql的index merge(索引合并)进行优化。本质上是分别两个字段分别走各自索引查出对应的数据,再将数据进行合并。

5) 选择性过低,直接走全表

选择性过低会导致索引失效。由于通过二级索引查询后还有回表查询的开销,如果通过该字段只能过滤少量的数据,整体上还不如直接查询数据表的性能,则MySQL会放弃这个索引,直接使用全表扫描。底层会根据表大小、IO块大小、行数等信息进行评估决定。

索引:
index(col_a)

SQL:

select * from table where col_a>'2017-10-22'

(三) SQL优化技巧

image.png

1 避免使用select *

select *不会走覆盖索引,会出现大量的回表操作,而从导致查询sql的性能很低。

2 用union all代替union

我们都知道sql语句使用union关键字后,可以获取排重后的数据。

而如果使用union all关键字,可以获取所有数据,包含重复的数据。排重的过程需要遍历、排序和比较,它更耗时,更消耗cpu资源。所以如果能用union all的时候,尽量不用union。

(select * from user where id=1) union all (select * from user where id=2);

3 小表驱动大表

小表驱动大表,也就是说用小表的数据集驱动大表的数据集。
假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。这时如果想查一下,所有有效的用户下过的订单列表。可以使用in关键字实现:

select * from order where user_id in (select id from user where status=1)

因为如果sql语句中包含了in关键字,则它会优先执行in里面的子查询语句,然后再执行in外面的语句。如果in里面的数据量很少,作为条件查询速度更快。

而如果sql语句中包含了exists关键字,它优先执行exists左边的语句(即主查询语句)。然后把它作为条件,去跟右边的语句匹配。如果匹配上,则可以查询出数据。如果匹配不上,数据就被过滤掉了。

select * from order where exists (select 1 from user where order.user_id = user.id and status=1)

4 批量操作

众所周知,我们在代码中,每次远程请求数据库,是会消耗一定性能的。而如果我们的代码需要请求多次数据库,才能完成本次业务功能,势必会消耗更多的性能。

insert into order(id,code,user_id) values(123,'001',100),(124,'002',100),(125,'003',101);

5 多用limit

有时候,我们需要查询某些数据中的第一条,比如:查询某个用户下的第一个订单,想看看他第一次的首单时间。

反例:

select id, create_date from order where user_id=123 order by create_date asc;

正例:

select id, create_date from order where user_id=123 order by create_date asc limit 1;

6 in中值太多

对于批量查询接口,我们通常会使用in关键字过滤出数据。比如:想通过指定的一些id,批量查询出用户信息。

sql语句如下:

select id,name from category where id in (1,2,3...100000000);

7 增量查询

有时候,我们需要通过远程接口查询数据,然后同步到另外一个数据库。

select * from user where id>#{lastId} and create_time >= #{lastCreateTime} limit 100;

按id和时间升序,每次只同步一批数据,这一批数据只有100条记录。每次同步完成之后,保存这100条数据中最大的id和时间,给同步下一批数据的时候用。

通过这种增量查询的方式,能够提升单次查询的效率。

8 高效的分页

有时候,列表页在查询数据时,为了避免一次性返回过多的数据影响接口性能,我们一般会对查询接口做分页处理。
在mysql中分页一般用的limit关键字:

select id,name,age from user limit 10,20;

如果表中数据量少,用limit关键字做分页,没啥问题。但如果表中数据量很多,用它就会出现性能问题。

比如现在分页参数变成了:

select id,name,age from user limit 1000000,20;

mysql会查到1000020条数据,然后丢弃前面的1000000条,只查后面的20条数据,这个是非常浪费资源的。
那么,这种海量数据该怎么分页呢?

优化sql:

select id,name,age from user where id > 1000000 limit 20;

9 用连接查询代替子查询

mysql中如果需要从两张以上的表中查询出数据的话,一般有两种实现方式:子查询连接查询
子查询的例子如下:

select * from order where user_id in (select id from user where status=1)

子查询语句可以通过in关键字实现,一个查询语句的条件落在另一个select语句的查询结果中。程序先运行在嵌套在最内层的语句,再运行外层的语句。

子查询语句的优点是简单,结构化,如果涉及的表数量不多的话。

但缺点是mysql执行子查询时,需要创建临时表,查询完毕后,需要再删除这些临时表,有一些额外的性能消耗。

这时可以改成连接查询。 具体例子如下:

select o.* from order o inner join user u on o.user_id = u.id where u.status=1

10 join的表不宜过多

根据阿里巴巴开发者手册的规定,join表的数量不应该超过3个。如果join太多,mysql在选择索引的时候会非常复杂,很容易选错索引。

11 join时要注意

我们在涉及到多张表联合查询的时候,一般会使用join关键字。
而join使用最多的是left join和inner join。

  • left join:求两个表的交集外加左表剩下的数据。
  • inner join:求两个表交集的数据。

使用inner join的示例如下:

select o.id,o.code,u.name from order o inner join user u on o.user_id = u.id where u.status=1;

12 控制索引的数量

众所周知,索引能够显著的提升查询sql的性能,但索引数量并非越多越好。
因为表中新增数据时,需要同时为它创建索引,而索引是需要额外的存储空间的,而且还会有一定的性能消耗。
阿里巴巴的开发者手册中规定,单表的索引数量应该尽量控制在5个以内,并且单个索引中的字段数不超过5个。

13 选择合理的字段类型

char表示固定字符串类型,该类型的字段存储空间的固定的,会浪费存储空间

我们在选择字段类型时,应该遵循这样的原则:

  1. 能用数字类型,就不用字符串,因为字符的处理往往比数字要慢。
  2. 尽可能使用小的类型,比如:用bit存布尔值,用tinyint存枚举值等。
  3. 长度固定的字符串字段,用char类型。
  4. 长度可变的字符串字段,用varchar类型。
  5. 金额字段用decimal,避免精度丢失问题。

14 提升group by的效率

我们有很多业务场景需要使用group by关键字,它主要的功能是去重和分组。

通常它会跟having一起配合使用,表示分组后再根据一定的条件过滤数据。

反例:

select user_id,user_name from order group by user_id having user_id <= 200;

这种写法性能不好,它先把所有的订单根据用户id分组之后,再去过滤用户id大于等于200的用户。
分组是一个相对耗时的操作,为什么我们不先缩小数据的范围之后,再分组呢?

正例

select user_id,user_name from order where user_id <= 200 group by user_id

使用where条件在分组前,就把多余的数据过滤掉了,这样分组时效率就会更高一些。

15 索引优化

sql优化当中,有一个非常重要的内容就是:索引优化
很多时候sql语句,走了索引,和没有走索引,执行效率差别很大。所以索引优化被作为sql优化的首选。
索引优化的第一步是:检查sql语句有没有走索引。
那么,如何查看sql走了索引没?
可以使用explain命令,查看mysql的执行计划。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值