1.尽可能少用 select *
secelt * from user where id=1;
很多时候,为了方便,我们会一次性把表中所有数据都查询出来,但不是每个场景都需要查询全部的,在实际业务中,我们需要的可能只有一两列,多余的我们没用到,白白的浪费了数据库的资源甚至内存或者CPU资源,此外,多查出来的数据,通过网络IO传输的过程中,也会增加数据传输的时间,还有一个最重要的问题就是,select * 不会走覆盖索引,会出现大量的回表操作,从而导致查询效率很低
secelt name,age from user where id=1;
2.用union all 代替 union
(select * from user where id =1;)
union
(select * from user where id =2;)
使用union关键字后,可以获取排除重复后的数据,如果使用union all关键字,可以获取所有数据,包含重复的数据,排除重复的数据需要遍历,排序和比较,它更耗时,更耗CPU资源,所以可以使用union all的时候,就不要使用union,除非有些特殊的场景,比如union all之后,结果集中出现了重复的数据,但业务场景中不能有重复的数据,就可以使用union
(select * from user where id =1;)
union all
(select * from user where id =2;)
3.小表驱动大表
select * from order where user_id in(select id from user where status =1)
select * from order where exists(select 1 from user where order.user_id = user.id and status = 1)
也就是说用小表的数据集驱动大表的数据集,假如有user和order两张表,order表中有10000条数据,user表中有100条数据,这时如果想查一下所有有效用户下过的订单列表,可以使用in或exists关键字实现这个需求,
这个场景下,in会更加适合一些,order是大表,有10000条数据,user表是小表,有100条数据,因为sql语句中包含了in关键字,则他会优先执行里边的子查询语句,然后在执行in外边的语句,如果in里边的数据量很少,作为条件查询速度更快,而如果sql语句中包含了exists关键字,它优先执行主查询语句,也就是左边的语句,把它作为条件,去根右边的语句匹配,如果匹配上则可以查询出数据,如果匹配不上,数据就被过滤掉。
总结一下就是:in使用与左边大表右边小表,exists适用于左边小表,右边大表
4.批量操作
for(Order order:list){
orderMapper.insert(order):
}
如果有一批数据经过业务处理之后,需要插入数据。以上为反例,在循环中逐条插入数据,该操作需要多次请求数据库,才能完成数据的插入.但在代码中,每次请求数据库,是会消耗一定性能的。而我们的代码需要多次请求数据库,才能实现当前业务需求。势必会消耗我们的性能
insert into order(id,code,user_id)
values(123,"001",100),(124,"002",101),(125,"003",102)
也不建议插入数据太多,如果数据太多数据库响应也会很慢,批量操作需要把握一个度,建议每批数据控制在500以内,如果数据多余500,则分批次处理
5.多用limit
select id, create_date from order where user_id=123 order by create_date asc;
我们需要查询某些数据的第一条,比如我们查询某个用户第一次的订单,想看看他的首单时间,反例如以上sql所示,根据id用户查询订单,按下单时间排序,先查出该用户所有的订单数据,得到一个订单集合,然后在代码中,取出第一个元素数据,即首单的数据,就能获取首单时间了,虽说这种做法在功能上没有问题,但它效率非常不好,需要先查询出所有数据,有点浪费资源.
优化:使用limit,只返回该用户下单时间最小的那一条数据即可.
select id, create_date from order where user_id =123 order by create_date asc limit 1;
此外,在删除或者修改的时候,为了防止误操作,导致删除了或者修改了不相干的数据,也可以在sql语句最后加上limit,比如我把id搞错了,也不会对太多的数据造成影响
update order set status=0,edit_time=now(3) where id>=100 and id<200 limit 100;
6.in条件的值太多
select id,name from category where id in(1,2,3,...1000000);
想通过一些指定的id批量查询出用户信息,sql如以上所示,如果我们不做任何限制,该查询语句可能一次性查询出非常多数据,容易导致接口超时,这时可以在sql中对数据用limit做限制
select id,name from category where id in(1,2,3,...100) limit 500;
不过更多的要是再业务代码中限制,如果代码中超过五百条数据,可以分批用多线程去查询数据,每批只查500条,最后 把结果汇总在一起返回,不过这只是一个临时方案,不适合id实在太多的场景,因为id太多,即使能快速查出数据,但如果返回的数据量太大,网络传输也是非常耗性能的,接口性能始终好不到哪里去
7.增量查询
select * from user;
有时候我们需要远程接口查询数据,然后同步到另一个数据库,反例如以上sql所示,如果直接获取所有的数据,然后同步过去,这样虽说非常方便,但是带来一个非常大的问题,如果查询数据很多的话,性能会非常慢
select * from user where id>#{lastId} and create_time >=#{lastCreateTime} limit 100;
我们可以按照id和时间升序每次只同步一批数据,这批数据只有100条数据,每次同步完成之后,保存这100条数据中最大的id和时间,给同步下一批数据用,通过这种增量查询的方式,能够提升单次查询的效率
8.高效的分页
select id,name,age from user limit 10,20;
有时候,列表页在查询数据时,为了避免一次性返回过多的数据影响接口性能,我们一般会对查询接口做分页处理,在数据库中分页一般用limit关键字,如果表中数据量少,用limit关键字没问题,但如果表中数据量很多,用limit关键字就会出现性能问题
比如分页参数是一千万
select id,name,age from user limit 1000000,20;
数据库会查到1000020条数据,然后丢弃前面的1000000条,只查后面的20条,这种就比较浪费资源
海量资源的话,先找到上次分页最大的id,然后利用id上的索引查询,
select id,name,age from user where id>1000000 limit 20;
不过该方案要求id是连续的,并且是有序的
还可以使用between分页
select id,name,age from user where id between 1000000 and 1000020;
需要注意的是between要在唯一索引上分页,不然会出现每页大小不一致的问题
9.用连接查询代替子查询
select * from order where user_id in(select id from user where status =1)
数据库中如果需要从两张表以上的表中查询出数据的话,一般有两种实现方式,子查询和连接查询,子查询如以上sql示例,子查询可以通过in关键字实现,一个查询语句的条件落在另一个select 语句的查询结果中,程序先运行在嵌套在内层的语句,再运行外层的语句,子查询语句的优点是简单,结构化,如果涉及的表数量不多的话。但缺点就是数据库执行子查询时,需要创建临时表,查询完毕后,需要删除这些临时表,有一些额外的性能消耗
select o.* from order o inner join user u on o.user_id =u.id where u.statys=1
这时可以改为连接查询,这样优化过后,性能会更高
10.join的表不宜过多
根据阿里巴巴开发者手册规定,join表的数量不应该超过3个。如果join太多数据库在选择索引时候会非常复杂,很容易选错索引,并且如果没有命中,nested loop join就是从两个表读一行数据进行两两对比,复杂度是2,所以我们应该尽量控制join表的数量
11.join时要注意
我们在涉及到多表联合查询的时候,一般会使用join关键字,而join使用最多的就是left join和inner join
left 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;
如果两张表使用inner join关联,数据库会自动选择两张表中的小表去驱动大表,所以性能上不会有太大的问题
select o.id,o.code,u.name
from order o
left join user u on o.user_id =u.id
where u.status=1;
如果两张表使用left join关联,数据库会默认使用left join关键字左边的表,去驱动它右边的表,如果左边的表数据很多时,就会出现性能问题。要特别注意的是在用left join关联查询时,左边要用小表,右边可以用大表,如果能用inner join的地方,尽量少用left join
12控制索引的数量
索引能够显著的提升查询sql的性能,但索引的数量并非越多越好,因为表中新增数据的时候,需要添加索引,而索引是需要额外的存储空间的,而且还会有一定的性能消耗,阿里巴巴的开发者手册中规定,单表的索引数量应该尽量控制在5个以内,并且单个索引中的字段数不超过5个,数据库使用的B+树的结构来保存索引的,在insert,update,delete操作的时候,需要更新B+树索引,如果索引过多,会消耗很多额外的性能
如果表中的索引太多,超过了五个,系统并发量不高的话,表中的数据也不多,其实超过五个也可以。只要不超过太多就可以。但对于一些高并发的系统,请务必遵守单表索引数量不要超过五个的限制
高并发系统:能够简历联合索引,就别建立单个索引,可以删除无用的单个索引,将部分查询功能迁移到其他类型的数据库中
13.选择合理的字段类型
char表示固定字符串类型 该类型的字段存储空间是固定的,会浪费存储空间
varchar表示变长字符串,该类型的字段存储空间会根据实际数据的长度调整,不会浪费存储空间。
如果是长度固定的字段,比如用户手机号,一般是十一位,可以定义为char类型,如果是企业名称类型,假如定义为char类型。如果定义为200字节,实际只有50字节,则会浪费150字节的存储空间,如果定义为50字节,实际为100字节,就会存储不下。而抛出异常
我们在选择类型的时候,应该遵循可以用数字类型,就不用字符串,因为字符的处理往往比数字要慢,尽可能用小的类型满足自身的需求
14.提升groud by效率
select user_id,user_name from order groud by user_id having user_id <=200;
我们有很多业务场景需要使用groud by关键字,他的主要功能是取出重复和分组,通常会和having一起使用,表示分组后再根据一定的条件过滤数据,以上这种写法性能不好,它先把所有得订单根据用户id分组之后,,再去过滤id大于等于200得用户,分组是一个相对耗时得操作。
我们可以先缩小范围在分组,使用where条件在分组前就把多余得数据给过滤掉了,这样分组效率就会高一些
select user_id,user_name from order where user_id <=200 group by user_id
索引失效的常见原因:
1.不满足最左前缀原则
2.范围索引列没有放最后
3.使用了select *
4.索引列上有计算
5.索引列上使用了函数
6.字符类型没有加引号
7.用is noo和is not null没注意字段是否允许为空
8.like查询左边有%
9.使用or关键字时没有注意