这篇主要介绍Thread API,也是并发编程中的基础
索引优化和设计原则
索引的优化原则
- 联合索引尽量所有字段全值匹配。如果使用部分字段,注意最左前缀规则,按照顺序使用联合索引。
- 不在索引列上做计算、函数、类型转换等操作,会导致索引失效走全表扫描。
- 尽量使用索引覆盖,减少select * 带来的回表的成本。
- mysql使用in、not in、is null、is not null、<> 、 !=,mysql的优化器会根据扫描的条数进行成本分析,可能直接会全表扫描,所以注意查询条件命中记录的条数。
- like注意使用前缀通配匹配。like ‘abc%’。如果没办法可要优化到索引覆盖级别。
- 如果不符合预期,可以强制force index使用索引。
- 避免文件排序(explain extra中有Using filesort)。为排序字段建立索引,或者where条件字段和排序字段能使用联合索引。排序还要避免多个排序字段的升序、降序规则不同。
如果不能避免filesort,则可以调整mysql的参数max_length_for_sort_data,调小一点来触发双路排序。filesort是在内存维护一个sort buffer,当要排序字段总大小大于这个值就会在buffer中只加载主键和排序字段,回表拿最后的结果,而不是在buffer中加载所有符合条件的数据的所有字段排序后返回。
- 分页优化
1
select * from table where name ='xxx' limit 100000,10;
此语句的过程是,扫描二级索引name,查出符合条件的主键值,回表查询聚簇索引,扫描100010行,取最后的10行。
深度分页会造成回表扫描大量聚簇索引才能取到深度的那几条数据,造成效率低下。
解决办法:
- 能用主键先用一个标签来过滤的场景来用主键id过滤。
改写为1
select * from table where id > 100000 limit 10;
这样能减少扫描聚簇索引的数量。但是有局限性,因为要求能找到这样一个标签值,且结果是按照主键这样排序的。
- 非主键排序的深度分页可以用延迟关联的方法来连接查询。
1
select * from table order by name limit 100000,5;
可以改写为
1 | select * from table a where inner join (select id from table b order by name limit 100000, 5) b on a.id = b.id; |
这样的连接查询中,子查询先是索引覆盖去排序去筛选出对应的记录且去除分页记录,然后再作为驱动表连接查询该表,此时是根据主键id做关联查询,可以大量减少回表扫描记录数。
- 连接查询的优化
- 连接查询的字段建立索引,驱动表记录去和被驱动表连接查询可以使用索引。
- 小表驱动大表,本身连接查询也是这样去选择驱动表的。
- 连接字段没有索引,mysql本身会有一个基于块的嵌套循环查询算法,在内存中有join_buffer区域去加载一批驱动表的数据,去和被驱动表做关联查询,减少磁盘IO的次数。
- count查询
- count(字段) 不为统计null的记录数。count(id)、count(1)、count(*)会统计null的记录。
- 本身这几个效率差不多,在count(字段)中的字段有索引情况下,可以直接扫描二级索引来完成统计,效率更高。
索引的设计原则
代码先行,索引后上
因为代码中要开发sql查询,可以根据业务中的sql来确定具体的sql来建立合适的索引。联合索引进来覆盖条件
索引要能覆盖大量查询场景(where、order by、group by),顺序也要按照最左前缀原则来设计。
- 不要在小基数的字段上建立索引
越小的基数在等值或者范围查询场景下扫描更多的记录,可能造成优化器选择全表扫描。 不能发挥索引的优势。
- where 和order by 冲突 尽量先满足where
- 索引字段的长度尽量小一点,相同大小空间的B+树能承载更多的数据,查找效率也更高。
设计模式——模板方法的应用
模板方法
模板方法
很多情况下代码中的业务都可以抽象出一个模板去解决,这时候经常需要用到模板方法。大家经常接触到的是一些业务方法的抽象模板,比如在计算优惠券的流程当中总是有一定的步骤:
(1)先计算该商品是否可以拥有优惠券信息
(2)再为该商品绑定优惠券信息
(3)最后回调或者通知向下的流程
今天要记录的是一个通用服务层的模板方法,包含了前置校验、后置处理(是有点像拦截器= =)、finally操作。
业务processor
- 可以定义一个domainProcessor去代表业务操作的processor接口,这个接口可以承接泛型。
1 | public interface DomainProcessor<T> { |
这里承接的泛型context是一个上下文的概念,指的是在一个业务处理processor中的上下文信息,其中可以有计算的参数,计算的结果和一些中间信息。
- 做一个抽象类,去将其中的前置操作、biz操作、后置操作、finally操作定义出来。
1 | public abstract class AbstractProcessor<T> implements DomainProcessor<T> { |
可以看到,这里定义了前置操作,这里可以去对biz要用的参数进行一个校验或者一些前置操作,同时将biz定义为了抽象方法,意图在为了让子类去继承时一定要去实现bizHandle这个方法。而exceptionHandle和finallyHandle方法则定义了异常的处理和最终要做的(比如线程快照的清除)。而整个process方法其实就是整个处理器的入口,即对这整个流程的一个编排。
可以看一个这个模板processor的的具体实现和测试类:
1 | public class AbstractProcessorTest { |
总结
这个模板可以作为之后一个处理器的抽象模板,能让代码逻辑很清晰的展现出来,也能解耦了各个处理模块,这里可以总结下。
ClassNotFoundException和NoClassDefFoundError
背景
极客时间上《Java核心技术36讲》第二讲中提到了一个问题:ClassNotFoundException和NoClassDefFoundError有什么区别?看到这个问题的时候,第一时间想到的就是一个是受检的异常,而另一个是一个Error,但是其实在真正的项目开发中这两个错误都遇到过,都是关于类或者文件jar包找不到的错误,这里去总结下其中的不同。