• 针对SQL语句里的where条件、order by条件以及group by条件去设计索引。最好让各个where、order by和group by和group

    后面跟的字段都是联合索引的最左侧开始的部分字段,这样他们都能用上索引。
    例如,快递订单索引设计,会根据APP短搜索比较频繁的条件来设计索引:
    (1)根据运单号搜索 创建运单号索引
    (2)根据收件人手机号搜索,多个订单按照时间进行排序 创建收件人手机号+时间联合索引
    (3)根据服务站ID+快递公司+状态进行搜索,多个订单按照时间进行排序 创建服务站ID+快递公司+时间 联合索引

  • 不要在枚举值字段建立索引,因为没法进行快速的二分查找,还不如全表扫描,建立索引尽量使用那些基数比较大的字段,就是值比较多的字段

  • 尽量对那些字段类型比较小的列来设计索引,比如tinyint之类,占用空间小,搜索性能更好

    1. 如果需要对比较长的字符串值设计索引,可以针对前20个字符建立索引,只是这种只适用于where条件查 询,不适用于order bygroup bygroup
  • 索引设计不宜过多,会影响增删改数据性能,建议两三个联合索引就够了

  • 不要在索引列上进行函数运算
  • 主键建议自增,不要用UUID,因为UUID会导致聚簇索引频繁页分裂。
  • 如果不能where和group by都用上索引,那么优先保证where用上索引,先使用where根据索引筛选出一部分数据,然后再进行排序
  • 把经常做范围查询的字段放到联合索引最后一个,因为一旦某个字段做范围查询用到了索引,那么接下来的字段都不能使用到索引。

重要原则:尽量利用一两个复杂的多字段联合索引,抗下80%以上的查询,然后用一两个辅助索引,抗下剩余的20%的非典型查询,保证99%以上的查询都能充分利用到索引,保证查询速度和性能。

索引设计案例实战:陌生人社交APP的MYSQL索引设计实战

搜索20-35岁的用户,按照评分进行排序:
select * from user_info where age between 20 and 25 order by score;
假设创建了一个联合索引(age, score),那筛选是可以用上索引来筛选,但是排序无法利用索引
那这时候是针对where去设计索引,还是针对order by设计索引?
一般这时候都是让where条件去使用索引来快速筛选出一部分指定的数据,然后进行排序,然后针对排序的数据拿出来一页数据
因为基于索引筛选往往可以筛选出想要的数据,如果数据量不太大的话,后面做排序分页的成本也不会太高。

那么需要把哪些字段包含到索引里面去?在联合索引里,字段的顺序要如何排列?
首先在联合索引里包含省份、城市、性别三个字段。
因为这个搜索条件是搜索频率最高的。
可以把基数较低但是查询频繁的省份、城市、性别三个字段放到联合索引最左侧去。(province, city ,sex)
如果需要增加年龄范围查询,可以将年龄字段放到联合索引最后(province, city ,sex, age)
如果不指定性别,而指定年龄进行查询,如select from user_info where province = xxx and city = xxx and age between xx and xx;
此时age没法用到索引,因为city和age之间隔了一个sex。此时可以加上sex字段,值设置为sex in (‘male’, ‘female’)
SQL语句:
select
from user_info where province = xxx and city = xxx and sex in (‘male’, ‘female’) and age between xx and xx;
此时保证所有字段都能用上索引
如果需要加上其他字段,则在age之前加即可,为什么非要让age在最后,因为age是范围查询,一旦SQL里面范围查询用到了索引,那么后面的条件都不能用到索引
如果需要带上一个字段,是否在7天内登录过,lastest_login_time。那么SQL语句里面可能会这么写lastest_login_time > xxx
如果lastest_login_time也加入联合索引,那么只有age范围查询能用上索引,lastest_login_time是用不了索引的,因为范围查询的时候只有第一个范围查询是可以用到索引,第一个范围查询后的范围查询是用不上索引的。
解决办法:可以在设计表的时候,设计一个字段:does_login_in_latest_7_days,int类型,表示这个人是否在最近7天登录过APP,然后将这个字段加入联合索引age前面即可。

模板方法

优点:

  • 定义算法骨架, 保证核心流程不被篡改,将一些步骤的具体实现交给子类;
  • 提取公共代码,便于维护、扩展
  • 行为由父类控制,具体由子类实现

缺点:

  • 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。

类结构图:
image.png

抽象父类Game:

  1. public abstract class Game {
  2. public abstract void initialize();
  3. public abstract void startPlay();
  4. public abstract void endPlay();
  5. //模板
  6. public final void play() {
  7. //初始化游戏
  8. initialize();
  9. //开始游戏
  10. startPlay();
  11. //结束游戏
  12. endPlay();
  13. }
  14. }

子类Cricket:

  1. public class Cricket extends Game {
  2. @Override
  3. public void initialize() {
  4. System.out.println("Cricket 游戏初始化");
  5. }
  6. @Override
  7. public void startPlay() {
  8. System.out.println("Cricket 游戏开始");
  9. }
  10. @Override
  11. public void endPlay() {
  12. System.out.println("Cricket 游戏结束");
  13. }
  14. }

子类FootBall:

  1. public class FootBall extends Game {
  2. @Override
  3. public void initialize() {
  4. System.out.println("FootBall 游戏初始化");
  5. }
  6. @Override
  7. public void startPlay() {
  8. System.out.println("FootBall 游戏开始");
  9. }
  10. @Override
  11. public void endPlay() {
  12. System.out.println("FootBall 游戏结束");
  13. }
  14. }

调用:

  1. public class TemplatePatternDemo {
  2. public static void main(String[] args) {
  3. Game cricket = new Cricket();
  4. cricket.play();
  5. Game footBall = new FootBall();
  6. footBall.play();
  7. }
  8. }

输出:

  1. Cricket 游戏初始化
  2. Cricket 游戏开始
  3. Cricket 游戏结束
  4. FootBall 游戏初始化
  5. FootBall 游戏开始
  6. FootBall 游戏结束

工厂模式

解决对象创建问题。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

类图:
image.png

接口Shape:

  1. public interface Shape {
  2. void draw();
  3. }

实现类Circle:

  1. public class Circle implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Circle draw");
  5. }
  6. }

实现类Rectangle:

  1. public class Rectangle implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Rectangle draw");
  5. }
  6. }

实现类Square:

  1. public class Square implements Shape {
  2. @Override
  3. public void draw() {
  4. System.out.println("Square draw");
  5. }
  6. }

生产Bean对象的工厂ShapeFactory:

  1. public class ShapeFactory {
  2. //使用getShape方法,根据类型获取对应的对象
  3. public Shape getShape(String shapeType) {
  4. if(shapeType == null){
  5. return null;
  6. }
  7. if(shapeType.equalsIgnoreCase("CIRCLE")){
  8. return new Circle();
  9. } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
  10. return new Rectangle();
  11. } else if(shapeType.equalsIgnoreCase("SQUARE")){
  12. return new Square();
  13. }
  14. return null;
  15. }
  16. }

demo测试:

  1. public class FactoryPatternDemo {
  2. public static void main(String[] args) {
  3. ShapeFactory shapeFactory = new ShapeFactory();
  4. Shape circle = shapeFactory.getShape("CIRCLE");
  5. circle.draw();
  6. Shape rectangle = shapeFactory.getShape("RECTANGLE");
  7. rectangle.draw();
  8. Shape square = shapeFactory.getShape("SQUARE");
  9. square.draw();
  10. }
  11. }

输出:

  1. Circle draw
  2. Rectangle draw
  3. Square draw

策略模式

优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。

类结构图:
image.png

Strategy策略接口:

  1. public interface Strategy {
  2. public int doOperation(int num1, int num2);
  3. }

求和策略OperationAdd:

  1. public class OperationAdd implements Strategy {
  2. @Override
  3. public int doOperation(int num1, int num2) {
  4. return num1 + num2;
  5. }
  6. }

求差策略:

  1. public class OperationSubstract implements Strategy {
  2. @Override
  3. public int doOperation(int num1, int num2) {
  4. return num1 - num2;
  5. }
  6. }

求乘积策略:

  1. public class OperationMultiply implements Strategy {
  2. @Override
  3. public int doOperation(int num1, int num2) {
  4. return num1 * num2;
  5. }
  6. }

使用 Context 来查看当它改变策略 Strategy 时的行为变化:

  1. public class Context {
  2. private Strategy strategy;
  3. public Context(Strategy strategy) {
  4. this.strategy = strategy;
  5. }
  6. public int executeStrategy(int num1, int num2) {
  7. return strategy.doOperation(num1, num2);
  8. }
  9. }

demo测试:

  1. public class StrategyPatternDemo {
  2. public static void main(String[] args) {
  3. Context context = new Context(new OperationAdd());
  4. System.out.println(context.executeStrategy(5, 10));
  5. Context sustractContext = new Context(new OperationSubstract());
  6. System.out.println(sustractContext.executeStrategy(15, 10));
  7. Context multiplyContext = new Context(new OperationMultiply());
  8. System.out.println(multiplyContext.executeStrategy(5, 10));
  9. }
  10. }

输出:

  1. 15
  2. 5
  3. 50

观察者模式:


解决问题:一个对象状态改变时,通知其他对象,考虑易用和低耦合,保证高度的协作。

优点: 1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。
缺点: 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化

类图结构:
image.png

Subject:

  1. public class Subject {
  2. //观察者列表
  3. private List<Observer> observers = new ArrayList<>();
  4. //状态
  5. private int state;
  6. public int getState() {
  7. return state;
  8. }
  9. public void setState(int state) {
  10. this.state = state;
  11. //状态更新时,通知所有的观察者
  12. notifyAllObservers();
  13. }
  14. /**
  15. * 增加观察者
  16. * @param observer
  17. */
  18. public void attach(Observer observer) {
  19. observers.add(observer);
  20. }
  21. /**
  22. * 通知所有的观察者
  23. */
  24. private void notifyAllObservers() {
  25. for (Observer observer : observers) {
  26. observer.update();
  27. }
  28. }
  29. }

观察者接口Observer:

  1. public abstract class Observer {
  2. protected Subject subject;
  3. public abstract void update();
  4. }

观察者1 BinaryObserver:

  1. public class BinaryObserver extends Observer {
  2. public BinaryObserver(Subject subject) {
  3. this.subject = subject;
  4. this.subject.attach(this);
  5. }
  6. @Override
  7. public void update() {
  8. System.out.println("Binary String:"+ Integer.toBinaryString(subject.getState()));
  9. }
  10. }

观察者2 HexaObserver:

  1. public class HexaObserver extends Observer {
  2. public HexaObserver(Subject subject) {
  3. this.subject = subject;
  4. this.subject.attach(this);
  5. }
  6. @Override
  7. public void update() {
  8. System.out.println( "Hex String: "
  9. + Integer.toHexString( subject.getState() ) );
  10. }
  11. }

观察者3 OctalObserver:

  1. public class OctalObserver extends Observer {
  2. public OctalObserver(Subject subject) {
  3. this.subject = subject;
  4. this.subject.attach(this);
  5. }
  6. @Override
  7. public void update() {
  8. System.out.println( "Octal String: "
  9. + Integer.toOctalString( subject.getState() ) );
  10. }
  11. }

调用demo:

  1. public class ObserverPatternDemo {
  2. public static void main(String[] args) {
  3. Subject subject = new Subject();
  4. new HexaObserver(subject);
  5. new OctalObserver(subject);
  6. new BinaryObserver(subject);
  7. System.out.println("First state change: 15");
  8. subject.setState(15);
  9. System.out.println("Second state change: 10");
  10. subject.setState(10);
  11. }
  12. }