文章目录
- (一) 建立索引的正确姿势
- 1 )索引不要包含选择性过低字段
- 2) 选择性高的字段前置或者单独建立索引
- 3)尽量使用覆盖索引
- (二) 使用索引的正确姿势
- 1) 最左匹配截断
- 2) 隐式转换
- 3) in+ order by 导致排序失效
- 4) or查询导致失效
- 5) 选择性过低,直接走全表
- (三) SQL优化技巧
- 1 避免使用select *
- 2 用union all代替union
- 3 小表驱动大表
- 4 批量操作
- 5 多用limit
- 6 in中值太多
- 7 增量查询
- 8 高效的分页
- 9 用连接查询代替子查询
- 10 join的表不宜过多
- 11 join时要注意
- 12 控制索引的数量
- 13 选择合理的字段类型
- 14 提升group by的效率
- 15 索引优化
(一) 建立索引的正确姿势
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优化技巧
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
表示固定字符串类型,该类型的字段存储空间的固定的,会浪费存储空间
我们在选择字段类型时,应该遵循这样的原则:
- 能用数字类型,就不用字符串,因为字符的处理往往比数字要慢。
- 尽可能使用小的类型,比如:用bit存布尔值,用tinyint存枚举值等。
- 长度固定的字符串字段,用char类型。
- 长度可变的字符串字段,用varchar类型。
- 金额字段用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的执行计划。