笔记.txt

UP 返回
地址 https://www.bilibili.com/video/BV1Av4y187hf/?vd_source=d4b23777458c9f0c9e0eb065a7a765b9

1.Java中线程实现方式
	继承Thread
	实现Runable	【这是究其底层的唯一方法,Thread也是实现了该接口】
	实现Callable重写call方法,配合FutureTask
	线程池构建线程

2.哲学家吃饭问题
	如果是5个哲学家,可以规定第0个吃饭时先拿右手再拿左手,其他人先拿左手再拿右手
	如果是无限个哲学家,可以规定第奇数个吃饭时先拿右手再拿左手,第偶数个先拿左手再拿右手
		!!@@202305191.png_492_438_1@@!!

3.线程的状态
	操作系统层面:新建 就绪 运行 等待 结束
		!!@@202305213.png_1129_317_1@@!!
	Java层面:新建 运行/就绪 阻塞(synchronized失败) 等待(调用wait) 时间等待(调用sleep join) 结束
		!!@@202305214.png_1139_555_1@@!!

4.如何停止线程
	stop 强制结束线程,不管在做什么,确实可以杀线程但是不推荐使用。
	使用共享变量,可以但是很少用
	interrupt 也是一种共享变量,在线程内维护。推荐该用法,如果线程在休眠时被中断将会抛出异常,可以供程序判断,更优雅的停止线程。相关的几个方法用法如下:
		interrupt()	线程默认interrupt标志位为false,调用该方法后置为true
		isInterruped()	获取interrupt标志位状态
		interrupted()	获取interrupt标志位状态,然后将其还原置为false

5.wait和sleep的区别
	sleep属于Thread的static方法,wait属于Object的方法
	sleep属于TIMED_WAITING,自动被唤醒;wait属于WAITING,需要手动唤醒
	sleep在持有锁时不会释放锁资源,wait会释放
	sleep在持有或者不持有锁都可以执行,wait只能在有锁时才可以执行
		wait方法会将持有锁的线程从owner扔到WaitSet集合中,这个操作是在修改ObjectMonitor对象,而加锁的对象的Markword里指向着该ObjectMonitor,所以如果没有持有synchronized锁的话,是无法操作ObjectMonitor对象的。ObjectMonitor属性如下:
		!!@@202305221.png_1093_619_1@@!!

6.并发编程的三大特性
	原子性:一个操作是不可分割的,不可中断的,一个线程在执行时,另一个线程不会影响到他
		保证原子性的方法:synchronized 		CAS		 Lock锁 		ThreadLocal(这个其实关系不大)
			!!@@202305222.png_990_376_1@@!!
	可见性:CPU不同核对数据的修改需要同步到所有核
		保证可见性:volatile		 synchronized			 Lock(两种加锁都只能保证在加锁时可见,后续的操作就不保证了) 		final(无法被修改,自然保证了)
	有序性:指令重排序
		保证有序性:volatile

7.什么是CAS
	!!@@202305225.png_1130_211_1@@!!
	优点:避免了线程的挂起和唤醒从而避免了频繁的内核态用户态切换
	缺点:执行时间过长,占用CPU(可以设定自旋次数,比如synchronized自旋多次以后升级重量级锁挂起,发现自旋过多还是挂起的好;或者使用LongAdder的方式)
			ABA的问题(可以额外加一个版本号来解决,比如AtomicStampedReference)
				!!@@202305223.png_1103_479_1@@!!

8.@Contended注解		https://www.bilibili.com/video/BV1Av4y187hf?p=8&spm_id_from=pageDriver&vd_source=d4b23777458c9f0c9e0eb065a7a765b9
	待深入了解(CPU缓存行的问题 ConcurrentHashMap和LongAdder都使用到了该注解,即填充无用数据是CPU缓存行不会受到其他数据的修改影响当前数据的性能)

9.Java四种引用类型
	强 软 弱 虚
	!!@@202305226.png_1137_681_1@@!!

10.ThreadLocal内存泄漏
	每一个线程都有一个成员变量ThreadLocalMap用来存储数据,ThreadLocal本身不存储数据,只是用来操作这个map,ThreadLocal就是这个map的key
	key内存泄漏:ThreadLocalMap的key是一个弱引用,所以在ThreadLocal失去引用时,ThreadLocal就可以被回收。Java已经帮我们解决了,如果这是个强引用,就会一直无法回收
		!!@@202305229.png_757_622_1@@!!
	value泄露:如果ThreadLocalMap的key已经被回收了,线程还未回收(比如线程池),这时value就一直存在导致泄露,所以使用完ThreadLocal后需要及时调用remove移除这个entry
		!!@@202305227.png_1120_510_1@@!!
	 ThreadLocal对象示意图:
		!!@@202305228.png_1019_473_1@@!!

11.Java锁分类
	可重入锁 不可重入锁:synchronized,ReentrantLock,ReentrantReadWriteLock都是可重入锁
	乐观锁 悲观锁:synchronized,ReentrantLock,ReentrantReadWriteLock都是悲观锁,CAS是乐观锁的一种实现
		!!@@2023052210.png_1070_267_1@@!!
	公平锁 非公平锁:synchronized只能实现非公平锁,ReentrantLock,ReentrantReadWriteLock可以实现公平锁和非公平锁
	互斥锁 共享锁:synchronized,ReentrantLock是互斥锁,ReentrantReadWriteLock有互斥也有共享锁

12.synchronized在jdk1.6中的优化
	锁消除:在synchronized修饰的代码中,如果不存在操作临界资源的情况,会触发锁消除(JIT优化,下同),即便写了也不会触发
		!!@@2023052211.png_505_112_1@@!!
	锁膨胀:如果在一个循环中,频繁的获取和释放锁资源,带来的消耗很大,锁膨胀就是将范围扩大,避免频繁的竞争和获取带来不必要的消耗
		!!@@2023052212.png_418_362_1@@!!
	锁升级
		!!@@2023052213.png_1081_523_1@@!!

13.synchronized实现原理
	 synchronized基于对象实现,堆内存中对象存在一个对象头,这里有一个MarkWord,对象的锁信息都存在这里。MarkWord中标记着四种锁的信息:
		!!@@2023052215.png_1058_643_1@@!!
	锁升级的步骤:
		!!@@2023052216.png_1033_253_1@@!!
	ObjectMonitor中的字段可以参照前面的图

14.什么是AQS		https://www.bilibili.com/video/BV1Av4y187hf/?p=14&spm_id_from=pageDriver&vd_source=d4b23777458c9f0c9e0eb065a7a765b9
	AQS就是AbstractQueuedSynchronizer类,AQS就是JUC包下的一个基类,JUC下的很多内容包括ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatch,Semaphore,CyclicBarrier都是基于AQS实现的
	AQS提供了一个由volatile修饰并且采用CAS方式修改的int类型的state变量
	AQS也维护了一个双向链表,每个节点都是Node对象,等待的线程都会加入这个队列,当线程wait时又会加入ConditionObject的队列中
	【参照addWaiter方法】
	
15.	AQS唤醒节点时为何从后往前找		https://www.bilibili.com/video/BV1Av4y187hf/?p=15&spm_id_from=pageDriver&vd_source=d4b23777458c9f0c9e0eb065a7a765b9
	从前往后找极大可能会错过某个节点:
		在插入节点时,是先将插入的prev指向tail节点,再将tail指向新节点。此时prev的节点的next还指向null,万一此时CPU卡在该位置,就会导致这个节点无法被发现	【参addWaiter方法
		在取消线程时,也是先调整prev的节点的指向	【参cancelAcquire方法
	所以节点的prev指针会更加优先,所以从后往前遍历寻找,使用的就是prev指针

16.ReentrantLock和synchronized区别
	ReentrantLock是类,synchronized是关键字,都是在JVM层面实现互斥锁
	效率上,如果竞争激烈,推荐ReentrantLock,不存在锁升级;而synchronized存在,且升到重量级锁不会再降级。但是后者毕竟还存在优化空间
	底层原理上,ReentrantLock基于AQS,synchronized基于ObjectMonitor
	功能上,ReentrantLock更全面,支持公平锁和非公平锁,可以尝试获取锁,还可以指定等待锁资源的时间
	使用上,ReentrantLock需要手动加解锁,synchronized直接关键字使用
	选择上,对并发如果很熟练,推荐ReentrantLock,否则推荐synchronized

17.ReentrantReadWriteLock
	读多写少的线程安全可以使用读写锁。
		!!@@202306171.png_674_681_1@@!!
	ReentrantReadWriteLock基于AQS实现,对state进行操作,拿到锁就执行,没拿到就去AQS队列排队
		读锁基于state的高16位操作,拿读锁时只要看看有没有读锁持有即可,有就说明此时没有写锁冲突
		写锁基于低16位操作,拿写锁时看看有没有人持有读写锁,没有的话低位+1即可
	ReentrantReadWriteLock也是可重入锁 
		写锁重入:写锁重入直接将state+1即可,只要确认持有锁资源的是当前写锁线程即可,只是因为只用了低位,所以写锁的范围会变小,但也够用了
		读锁重入:读锁是共享锁,对state高位+1,同一时间会有多个线程持有读锁,这样就不知道每个线程的重入次数。为了记录线程重入次数,每个读线程会有一个ThreadLocal记录重入的次数
	写锁的饥饿问题:读锁只用对state+1就可以拿到锁,如果此时写锁来了,可能会一直拿不到锁;所以读锁拿到锁后,后续的读线程需要去AQS队列排队,此时队列前面如果有需要写锁的线程,那么后续的读锁线程是无法拿到锁的。持有读锁的前程,只会让写锁之前的读线程拿到锁资源

18.jdk提供了哪些线程池
	newFixedThreadPool		new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
		固定线程个数的线程池,给的参数就是核心线程数,同时也是最大线程数。线程是懒加载,一开始并未构建,随着任务的提交才会不断构建,如果线程都已构建好,此时任务会被放到LinkedBlockingQueue无界队列存放,等待线程从队列中去take出任务执行
	newSingleThreadExecutor 	new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
		单例线程池。因为只有一个线程,所以消费的顺序是和任务提交的顺序一致的
	newCachedThreadPool		new  ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>())
		缓存线程池。没有核心线程,任务来的时候直接丢入队列,此时触发构建一个工作线程来执行,这个工作线程结束60s后没有任务执行就会结束。他的特点是任务只要提交进来就必然有工作线程可以处理。一般不推荐该线程池,因为成本高每次都得创建线程
	newScheduledThreadPool	new ScheduledThreadPoolExecutor(corePoolSize)
		定时任务线程池。可以以一定周期去执行任务,或者延迟一段时间执行任务一次;基于DelayQueue实现延迟执行,周期性执行则是任务执行完毕后再次扔回阻塞队列
	newWorkStealingPool		基于ForkjoinPool构建的线程池
		传统的ThreadPool所有的任务都会放在同一个阻塞队列中,forkjoinpool的每一个线程都有自己的阻塞队列,当一个线程的队列中没有任务时,他会去其他的线程阻塞队列中窃取任务执行,从而没有工作线程处于空闲状态,使用率高
		ForkjoinPool可以指定任务的拆分方式,等任务fork完毕以后再一起执行,最后join起来形成最终结果
	一般不推荐使用jdk自带的线程池,因为很多参数无法自定义

19.线程池的核心参数
	ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlcokingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutorHandler handler)
	!!@@202306181.png_815_231_1@@!!
	KeepAliveTime和unit决定了非核心线程在多长时间没有任务后会被销毁
	当任务来时,如果核心线程还没到达最大值,则会创建新的核心线程执行;否则就会放进阻塞队列中workQueue
	ThreadFactory可以用来自定义线程工厂,方便定位
	当任务来时,如果核心线程已经满了,阻塞队列也满了,最大线程数已经达到,就会触发拒绝策略handler。有以下几种策略:
		AbortPolicy	默认策略,直接抛异常
		CallerRunsPolicy		无法处理时,将任务交给调用者处理
		DiscardPolicy		直接丢弃
		DiscardOldestPolicy	丢弃队列中最早的任务,再次尝试将该任务交给线程池处理
		自定义Policy		根据自己的业务,可以将任务丢到数据库,也可以做别的操作

20.线程池的状态(重点)
	线程池的ctl是核心属性,其中高3位表示线程池状态,低29位表示工作线程个数
	!!@@202306182.png_843_511_1@@!!!!@@202306184.png_840_453_1@@!!	!!@@202306185.png_910_473_1@@!!
	线程池只有在RUNNING时才是正常状态,此时如果执行shutdown,就会进入SHUTDOWN,表现见图
	在RUNNING或者SHUTDOWN时执行shutdownNow,就会进入STOP,同时会把阻塞队列中的任务返回出去,表现见图
	TIDYING只是一个过渡状态,执行了一个terminated方法后进入最终的TERMINATED,terminated是个空方法,可以自定义实现想做的事
	execute方法中有很多地方都对线程池的状态做判断,详细可见21的内容

21.线程池的执行流程(重点,execute方法)
	核心代码如下:
	!!@@202306191.png_802_490_1@@!!	
				!!@@202306194.png_801_345_1@@!!
				!!@@202306193.png_858_575_1@@!!
	流程图如下:
	!!@@202306195.png_1429_666_1@@!!

22.线程池添加工作线程的流程(addWorker方法)
	addWorker中主要分为两块来看:
	1.校验线程池的状态太以及工作线程个数:
		!!@@202306196.png_884_495_1@@!!
		!!@@202306199.png_876_378_1@@!!
		!!@@2023061910.png_881_261_1@@!!
	 2.添加工作线程并启动:
		!!@@2023061911.png_872_439_1@@!!
		!!@@2023061912.png_876_479_1@@!!
		!!@@2023061913.png_880_596_1@@!!

23.线程池为何要构建空任务的非核心线程

24.线程池使用完毕为何必须shutdown()
	如果使用完线程池没有及时调用shutdown或者shutdownNow,会造成内存泄漏,因为线程属于GCRoot对象,核心线程如果无法回收,导致Worker对象也无法回收,进而导致线程池对象也一直无法回收

25.线程池的核心参数如何设置
	线程池主要就是设置好核心线程数和拒绝策略
	线程有CPU密集型,io密集型,或者混合型,无法直接根据公式来设置参数,需要通过不同的测试表现,根据实际的硬件情况,来决定如何设置线程池参数
	线程池提供了很多get和set方法,让我们可以动态监控以及修改,这样就可以通过不断调整来达到最适合的设置
		!!@@2023061914.png_804_250_1@@!!
	关于线程池的监控和修改可以使用一些开源项目,比如hippo4j,可以喝springboot整合(可以去github搜索)

26.ConcurrentHashMap在1.8做了什么优化
	存储结构优化(链表转红黑树)
	写数据加锁优化
	扩容优化
	计数器优化

27.ConcurrentHashMap的散列算法
	hashmap和ConcurrentHashMap的数组长度都是2的n次方,其散列方法spread中,有一句
		return (h ^ (h >>> 16)) & HASH_BITS;
	目的是为了让被散列的hash值高位低位都能参与到运算;同时结果不能为负数,因为负数有特殊的含义

28.ConcurrentHashMap初始化数组的流程
	
29.ConcurrentHashMap扩容流程
	扩容触发的三种方式:1.列表转红黑树的时候 2.putAll 3.普通的add元素

30.ConcurrentHashMap读取数据的流程
	get方法不会加锁

31.ConcurrentHashMap计数器的实现
	计数器是为了计数map中的元素个数。ConcurrentHashMap为了应对高并发,不但有一个baseCount计数,还有一个数组counterCell在并发量大的时候计数(类似于LongAdder)
	通过size方法统计元素个数时,需要将baseCount和counterCell中的数据一起累加获取最终的个数(但是一致性是弱一致的,还不太理解)

32.对spring的理解
	发展历程:
		!!@@202306242.png_1318_582_1@@!!
	组成:
		!!@@202306244.png_952_388_1@@!!
	好处:
		!!@@202306245.png_940_474_1@@!!

33.spring用了哪些设计模式
	单例模式:scope= "singleton";默认也是单例
	原型模式:scope= "propotype";克隆对象
	模板模式:jdbcTemplate就是这样实现的,jdbc的步骤是固定的:加载驱动 获取连接通道 构建SQL语句 执行SQL语句 关闭资源。其中构建和执行SQL语句是不同的,就留给用户自己实现,其他步骤都是一样的
				还有spring的refresh也留了一些bean的行为的接口让用户自定义
	观察者模式:主要用在事件里,也可以理解为发布订阅,用的地方有ApplicationListtener,包括 初始化事件发布器 检查并且注册相关的监听器 发布事件
	工厂模式:简单工厂模式:工厂根据参数决定生产哪个对象,比如getBean就是根据id或name来获取
				工厂方法模式:一般bean的实例化直接交给容器管理,另一种就是将创建过程交给一个工厂实现,而spring容器管理这个工厂,这就是工厂模式,spring有两种实现,静态工厂方法和动态工厂方法
				!!@@202306246.png_963_784_1@@!!
	适配器模式:将一个类的接口转换成用户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的类可以在一起工作。spring中在aop实现中的Advice和Intercepor之间的转换就是通过适配器模式实现的
				!!@@202306247.png_767_352_1@@!!
	装饰者模式:又称为包装模式(Wrapper),作用是用来动态的为一个对象增加新的功能,装饰模式是一种用于代替继承的技术。spring中用到包装器模式的在类名上就包含Wrapper或者Decorator
				!!@@202306248.png_732_359_1@@!!
	代理模式:aop
	策略模式:策略模式对应于解决某一个问题的一个算法族,允许用户任选一个解决问题,同时可以方便的更换算法或者增加新的算法。spring在实例化对象的时候用到Strategy模式,XMLBeanDefinitionReader PropertiesBeanDefinitionReader
	责任链模式:aop中的拦截器链
	委托者模式:DelegatingFilterProxy 整合Shiro SpringSecurity都有用到

34.Autowired和Resource区别
	都可以写在字段或者setter方法上,如果写在字段上就不用写setter方法
	Autowired是spring提供的,只按照类型注入,默认非空,如果可以为空可以设置required为false;如果想按照名称装配,可以结合Qualifier注解使用
	Resource由j2EE提供,默认按照byName注入,有name和type属性,指定哪个就按照哪个注入。如果同时指定,则从spring上下文寻找唯一匹配的bean装备,找不到抛异常

35.spring常用注解
	@Controller @Servcie @RestController @RequestBody ...
	springboot的启动类上有一个@ComponentScan注解,启动时会自动扫描Application所在包下的所有类的注解,此时如果类很多的话就可能很慢,这是我们可以在pom中引入一个依赖spring-context-indexer	
	编译一下就会发现META-INF下会生成一个文件spring.componens,记录了所有的注解类,这样在编译期就会搜集所有注解类,启动时就不用去一个个读Application所在包下的类的注解了
		!!@@202306281.png_531_111_1@@!!	!!@@202306282.png_1414_566_1@@!!
	
36.循环依赖的理解
	如果是通过构造器造成的循环依赖,那无解。即A的构造方法中有new B的操作,B中有new A,或者AB的成员变量也直接懒汉模式,这样不论在创建哪一个时都需要new另外一个,此时必然循环依赖出现栈溢出异常
	所以我们要将类的创建和初始化分开,在创建完以后先将对象的引用提前暴露出来,然后再去初始化他的成员变量。具体提来实现的话:
		首先AB类的成员变量中不要直接new,而是提供set方法来设置
		然后我们需要维护一个map,这个map里就是各个对象暴露出来的引用,当用getBean获取A对象时,通过beanName如果发现map中没有这个对象,那么就创建A(使用反射),并将其引用放入map,如果有就返回该引用。
			在后续初始化成员变量时,同样去map中找B对象,如果有就直接用,这样如果先前获取过B对象这时map就肯定有他的引用了;如果没有就走getBean来获取B,此时map里肯定有了A对象的引用了,那么B的创建就可以完成初始化,再回来A也完成了初始化
			!!@@202306289.png_1036_436_1@@!!!!@@2023062810.png_998_310_1@@!!!!@@202306287.png_952_412_1@@!!

37.spring是如何解决循环依赖的
	对于构造注入都是无解,会检测出兵抛异常;对于设置注入,仅单例支持(三级缓存-提前暴露),原型模式不支持。因为设置注入如果要支持就必须提前暴露对象,而原型模式用完对象就没用了,可是暴露的对象在map里又不会回收,会出问题
		!!@@2023062811.png_1278_684_1@@!!

38.spring中构造注入的循环依赖问题
	构造方法的循环依赖是无解的,所以只要检测抛异常即可。在创建对象的时候可以用一个容器存放当前创建的对象,先创建A,再创建B,再创建C,如果C里又有成员A,这时就会在容器中找到A,发现循环依赖了,就可以抛出。创建完毕以后将容器删除即可
	isPrototypeCurrentlyInCreation
		!!@@202306302.png_1232_561_1@@!!

39.spring循环依赖三级缓存的问题
	bean的生命周期	左上角缺了一个 实例化Spring容器
		!!@@202306303.png_834_686_1@@!!
	spring通过三级缓存的方式来处理循环依赖,即在创建对象后暴露对象,之后再初始化。放入一级缓存时需要清空二级和三级缓存
		!!@@202306304.png_853_695_1@@!!
	源码中的流程如下:
		!!@@202306305.png_977_664_1@@!!
	
	调用构造方法后放入三级缓存:
		!!@@202306306.png_823_490_1@@!!
	放入三级缓存的逻辑:
		!!@@202306307.png_869_399_1@@!!
	在填充属性的时候会存入二级缓存,最后把创建的对象放入一级缓存:
		!!@@202306308.png_849_465_1@@!!
	
	为什么需要三级缓存?
		三级缓存主要处理AOP的代理对象,存储的是一个ObjectFactory。三级缓存考虑的是代理对象,而二级缓存考虑的是性能。从三级缓存的工厂里创建出对象再扔到二级缓存,这样就不用每次从工厂里拿
	没有三级缓存能解决循环依赖吗?
		可以,但是只能存储aop之前的原型对象,无法实现代理
	三级缓存分别什么作用:
		一级:正式对象 
		二级:半成品对象 
		三级:工厂
		!!@@202306309.png_1057_458_1@@!!

40.spring中bean的生命周期
	!!@@2023063010.png_1443_822_1@@!!

41.spring支持几种作用域
	spring容器中的bean可以分为5个范围:
	1.propotype 为每一个bean请求提供一个实例
	2.singleton 默认,每个容器中只有一个bean实例,单例的模式由BeanFactory自身来维护
	3.request 为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收
	4.session 与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效
	5.global-session 全局作用域,global-session和Portlet应用相关,当你的应用部署在Portlet容器中工作时,它包含很多Portlet。。。。
		!!@@202307012.png_685_206_1@@!!

42.spring的事务隔离级别
	!!@@202307013.png_965_929_1@@!! !!@@202307014.png_965_339_1@@!!

43.spring中事务传播属性
	required就是AB共用事务,requires-new就是AB各自独立事务
	!!@@202307015.png_963_496_1@@!!

44.spring事务的实现方式
	编程式事务管理:编程的方式管理实务,很大的灵活性但难管理
	声明式事务管理:使用spring提供的注解或配置文件
	分布式事务

45.spring中事务的本质	具体看视频链接
	!!@@202307016.png_960_244_1@@!!
	
46.BeanFactory和ApplicationContext的理解
	BeanFactory:Bean工厂→IOC容器
	BeanDefinition:Bean定义
	BeanDefinitionRegistry:BeanDefinition和BeanFactory的关联
	...
	ApplicationContext:应用上下文
	ApplicationContext ac = new ClasspathXmlApplicationContext(xxx.xml)

47.BeanFactoryPostProcessor的理解
	bean工厂的后置处理器,实在BeanFactory创建完成后的后置处理
	BeanFactory对外提供bean对象,需要知道怎么提供bean对象,这个靠的是BeanDefinition:通过xml或者注解,BeanDefinition注册,完成BeanFactory的处理
	比如有一个类含有@Configuration注解,表明这是一个配置类,但是在前面的过程中其实并不会加载解析这个注解,他的加载实际就是在后置处理器里
	
	在refresh方法中(可以从ClassPathXmlApplicationContext中的构造方法中找到)
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()		完成bean工厂对象的创建,同步会完成配置文件的加载解析
		postProcessBeanFactory(beanFactory)		bean工厂的后置处理,是一个接口protected空方法,可以看到很多的实现
		invokeBeanFactoryPostProcessors(beanFactory)		调用bean工厂的后置处理器
			该方法第一行就是在调用
				invokeBeanDefinitionRegistryPostProcessors,这个方法里有个循环,里面执行的就是一个接口方法PostProcessBeanDefinitionRegistry
				其中一个实现就是ConfigurationClassPostProcessor,具体的解析方法在parse中,可以看到很多针对配置类的注解的处理判断
				
48.BeanPostProcessor的理解
	针对bean对象初始后处理操作,BeanPostProcessor提供了一种扩展机制,在bean对象完成之后给他一些增强。实现方式:自定义接口的实现,注册到bean工厂的map中
	这样springIOC的核心流程就如下:
		首先有bean的定义被封装到BeanDefinition中→注册BeanDefinition→BeanFactory在创建实例的时候会从注册器中获取相关的bean定义→通过BeanFactoryPostProcessor完成bean工厂的后置处理→得到单例bean对象→
		遍历上述map执行相关行为→AOP→代理对象,代理对象会有一些增强
	
49.springMVC的理解
	前端控制器,针对servlet进行了一层封装。流程如下:
	!!@@202307021.png_2026_975_1@@!!	
	    前端浏览器发送请求,会被DispatcherServlet捕获(图例可以看到细化流程,包括processRequest),然后通过HandlerMapping(处理器映射器)找到合适的处理器(返回时也会经过多个拦截器),
	拿到处理器后再通过HandlerAdapter(处理器适配器)找到对应的controller(Handler处理器)做请求的处理,返回modelAndView对象给前端控制器,
	对象给前端控制器在通过视图解析器(ViewerResolver)将其渲染到页面
	
	!!@@202307022.png_1947_691_1@@!!

50.spring和springmvc的关系	  具体的代码可以参看视频,包括两者的父子关系
	在spring和springmvc整合的项目中,两者都有一个IOC容器,其中controller定义的实例由springmvc的容器维护,service和dao的实例则由spring的容器维护
	同时,spring容器是springmvc的父容器,所以controller可以获取service对象,而反过来则不行
	
51.DelegatingFilterProxy的理解
	
52.springboot自动装配原理
	springboot启动的run方法就是进行了spring容器的初始化,没什么特别的,主要是靠注解@SpringBootApplication,里面有一个@EnableAutoConfiguration
		!!@@202307023.png_807_598_1@@!!
	EnableAutoConfiguration注解有一个@Import注解,value为AutoConfigurationImportSelector.class,这个类的selectImport方法中,继续进入getAutoConfigurationEntry方法,这里面就会完成自动装配的处理
		getCandidateConfigurations方法获取所有候选的配置信息,会加载META-INF/spring.factories下key为EnableAutoConfiguration的所有类到内存中
		!!@@202307025.png_1614_532_1@@!!	!!@@202307024.png_1557_600_1@@!!	!!@@202307026.png_739_308_1@@!!
	从以下的路线去答:@SpringApplication注解→@Configuration→ConfigurationClassPostProcessor→@Import注解→延迟加载→自动装配(spring.factories)→SPI 去重 排序 过滤

53.Import注解的理解
	spring3.0时提供,目的是为了替换在xml配置文件中的import标签,所以在Configuration里可以通过这个注解来导入第三方的配置类,同时又扩展了以下功能:
	1.可以把某个类型的对象注入到容器中
	2.导入的类型如果实现了ImportSelector接口,那么会调用接口声明的selectImports方法,将方法返回的类型全类路径的类型对象注入到容器中
	3.如果导入的类型实现了ImportBeanDefinitionRegistrar接口,那么就会调用声明的方法在该方法中显示地提供注册器来完成注入
	用处解释可以直接看这个注解的javadoc;代码层面看之前后置处理器的ConfigurationClassPostProcessor的processConfigBeanDefinitions方法的parse方法,再到processImports方法即可看到他的处理逻辑
		!!@@202307027.png_1179_341_1@@!!

54.DeferredImportSelector的理解		代码解释参视频
	springboot使用了ImportSelector,但是没有直接用,而是继承他用了DeferredImportSelector,在这个类中进行了延迟加载(延迟注入bean实例的作用)
	自动装配的核心是加载所有依赖中的META-INF/spring.factories文件中的配置信息。我们那可能有多个需要加载的文件,就需要多次操作,所以可以考虑把所有的信息都加载后再统一把这些需要注入到容器中的内容注入进去

55.bootstrap.yml的作用
	!!@@202307031.png_717_203_1@@!!

56.如果要对属性文件中的账号密码加密如何实现
	1.自定义监听器
	2.声明后置处理器(implements EnvironmentPostProcessor),将加载的配置信息重新解密后放回覆盖即可

57.Indexed注解的理解
	该注解解决的问题是:随着项目越来越复杂,@ComponentScan需要加载的class会越来越多,系统启动时会造成性能损耗,所以indexed注解就是为了提升系统启动性能
	参35,当引入对应的依赖时,在系统编译的时候,会主动收集所有被@Indexed注解标识的Java类,然后记录在META-INF/spring.components文件中
	可以看到Component注解本身就含有了Indexed注解

58.@Component @Controller @Repository @Service的区别
	!!@@202307032.png_847_335_1@@!!

59.spring中的aop有哪些通知类型
	!!@@202307033.png_862_274_1@@!!

60.什么是spring的依赖注入
	!!@@202307034.png_845_313_1@@!!

61.spring框架中的单例是线程安全的吗
	不是。首先如果是线程安全的,那么势必会造成性能损耗;另外也是没必要的,因为对象一般都会设置成无状态的,线程安不安全并不影响用途

62.@ComponentScan注解是干嘛的
	指定需要扫描的包,不写默认扫描启动类所在的包及下面的包

63.@EnableAutoConfiguration注解是干什么的
	开启自动装配。本身这个注解就有@Import,同时还有一个@AutoConfigurationPackage(也是含有@Import),就是为了导入一些类

64.bootstrap.yml的意义		同55
	springboot默认支持的属性文件有:application.properties application.xml application.yml application.yaml
	而bootstrap.yml bootstrap.properties默认是不支持的,需要在springcloud环境下才有用,作用是在springboot项目启动前启动一个父容器,该容器可以在springboot容器启动之前完成一些加载初始化的操作,比如加载配置中心的信息

65.Import注解的三种使用方法	同53

66.RequestMapping和GetMapping的区别
	RequestMapping具有类属性,可以进行get post put或者其他的注释中具有的请求方法。GetMapping是get请求方法中的一个特例,他只是RequestMapping的一个延伸,目的是为了提高清晰度

67.springboot的核心注解是哪几个,他主要由哪几个注解组成
	启动类上的注解@SpringbootApplication,就是核心注解,主要包含以下3个注解:
	@SpringBootConfiguration 组合了@Configuration注解,实现配置文件功能
	@EnableAutoConfiguration 打开自动配置功能,也可以关闭某个自动配置的选项。如需要关闭数据源自动配置功能:@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
	@ComponentScan spring组件扫描

68.springboot可以兼容老spring项目吗
	
69.springboot如何定义多套不同环境配置
	1.创建各环境的properties文件
		application.properties application-dev.properties application-test.properties application-prod.properties
	2.在application.properties中指定当前的环境 spring.profiles.active = test,此时生效的就是application-test.properties

70.springboot需要独立的容器运行吗
	可以不用,内置了Tomcat Jetty等容器

71.springboot有哪几种读取配置的方式
	默认读取的配置文件为application.properties application.yml,查找顺序先从application.properties查找
	
	@PropertySource 用于指定资源文件的读取位置,不仅能读取properties文件,也能读取xml,并且通过yaml解析器配合自定义PropertySourceFactory实现解析yaml文件
	@Value 可以直接在对象属性上使用该注解,同时以${} 的形式传入配置文件中对应的属性,同时需要在该类的上方使用@Configuration注解,将该类作为配置。此方法适用于对象的参数比较少的情况
	@Environment springcore中一个用于读取配置文件的类,将此类使用@Autowired注入到类中就可以使用他的getProperty方法获取某个配置项的值
	@ConfigurationProperties 该注解声明当前类为配置读取类,prfix="rabbitmq"表示读取前缀为rabbitmq的属性。此方法适用于参数较多的情况,不用每一个字段都加@Value

72.springboot支持哪些日志框架,推荐和默认的日志框架是什么
	支持Java util Logging,Log4j2,Logback;默认使用logback,如果选择了其他框架需要将这个给排除掉

73.springboot springmvc和spring的区别
	spring最重要的特征是依赖注入和IOC控制反转,可以基于此开发松耦合应用
	springmvc提供了一种分离式的方法来开发web应用
	springboot通过一个自动配置和启动的方式来解决spring和springmvc繁多的配置项

74.springboot中的监视器是什么
	
75.springboot打成的jar和普通的jar有什么区别
	springboot打的包是可执行的jar,但是不可以作为普通的jar被其他项目依赖,即使依赖了也无法使用其中的类,因为他们结构不同
	普通的jar解压后就是包名,包里就是代码。springboot打的jar解压后,\Boot-INF\classes下才是代码,因此无法被直接引用。如果非要引用,可以在pom中增加配置,将springboot打成两个jar,一个可执行一个可引用

76.springboot的run做了什么事
	IOC初始化的操作

77.springboot的优点
	独立运行 springboot内嵌了各种servlet容器,不需要打成war部署
	简化配置 spring-boot-startter-web启动器自动依赖其他组件,减少了maven的配置
	自动配置 能根据当前类路径下的类 jar来自动配置bean,如添加一个spring-boot-startter-web启动器就能拥有web的功能,无需其他配置
	无代码生成和xml配置 springboot配置过程中无代码生成,也无需xml配置文件就能完成所有配置,这一切都是基于条件注解完成的
	应用监控 提供一系列端点可以监控服务应用,做健康检测

78.springboot如何解决跨域问题
	!!@@202307161.png_1505_583_1@@!!

79.springboot如何配置log4j
	先排除项目创建时自带的日志logback,然后再引入log4j,引入依赖之后,去src/main/resources下配置log4j-spring.properties文件,就可以开始对应用的日志进行配置使用

80.springboot如何实现定时任务
	1.使用spring中的@Scheduled注解(单体应用)
	2.使用quartz,按照quartz的方式,定义job和trigger即可(分布式应用)

81.springboot自动装配的核心配置文件有哪些
	debug启动springboot项目,在AutoConfigurationImportSelector的getAutoConfigurationEntry方法中打断点即可看到springboot如何读取配置类的
	META-INF/spring.factories	获取候选配置类
	META-INF/spring-autoconfigure-metadata.properties 过滤没用的配置类

82.springboot自动装配的流程
	springboot执行run方法,进行IOC的初始化。springboot不会进行配置文件的初始化,而是注解初始化。会将Java配置类的对象传递进去,走到@SpringbootApplication注解,接下来起作用的是@EnableAutoConfiguration
  这个注解会去加载spring.factories和spring-autoconfigure-metadata.properties这两个配置文件,进行候选和筛选,加载进内存后,实际上是在AutoConfigurationImportSelect中加载这两文件
  在返回的时候加载进入容器,这就是自动装配的流程

83.几个常用的starter
	spring-boot-starter-web	提供web开发需要servlet与jsp支持,以及内嵌的Tomcat
	spring-boot-starter-data-jpa	提供springjpa+hibernatte
	spring-boot-starter-data-Redis	提供Redis
	mybatis-spring-boot-starter	第三方的mybatiss集成starter
	spring-boot-starter-data-solr	solr支持

84.如何理解springboot配置加载顺序
	  !!@@202307162.png_1319_799_1@@!!

85.如何实现springboot应用程序的安全性
	使用spring-boot-starter-security依赖,并且必须添加安全配置。配置类必须扩展WebSecurityConfigurerAdapter并覆盖其方法

86.如何在springboot启动时运行一些特定的代码
	实现接口ApplicationRunner或者CommandLineRunner,这两个接口的实现方式一样,都只提供了一个run方法,实现他们的类加入IOC容器即可生效

87.springboot如何热部署
	引入devtools依赖,但是该模块在生产环境中禁用
	!!@@202307171.png_1335_739_1@@!!

88.什么是spring boot starter
	!!@@202307172.png_1330_206_1@@!!

89.什么是springboot

90.如何连接一个像MySQL或者oracle一样的外部数据库
	以MySQL为例:
	1.首先将MySQL连接器的依赖添加进pom.xml
		!!@@202307173.png_874_177_1@@!!
	2.从pom中移除h2的依赖,或者至少把他作为测试范围
		!!@@202307174.png_769_310_1@@!!
	3.配置MySQL数据库连接(配置application.properties)
		!!@@202307175.png_724_142_1@@!!
	4.重新启动即可

91.运行springboot项目的方式
	打包用命令或者放到容器中运行
	用maven/gradle插件运行
	直接执行main方法

92.什么是buffer pool
	buffer pool:缓冲池,是用来缓存表数据与索引数据,减少磁盘io操作提升效率
	buffer pool由缓存数据页(Page)和对缓存数据页进行描述的控制块组成,控制块中存储着对应缓存页的所属的表空间、数据页的编号以及对应缓存页在buffer pool中的地址等信息
	buffer pool默认大小是128M,以Page页为单位,Page页默认大小16K,而控制块的大小约为数据页的5%,大概800字节
		!!@@202307177.png_1150_797_1@@!!
	虽然查询时可能只要一条数据,但是其实加载的是一个缓存页

	如何判断一个页是否在bufferPool中缓存?
		MySQL中有一个hash表结构,使用表空间号+数据页号作为key,然后缓冲页对应的控制块作为value
			!!@@202307178.png_582_384_1@@!!
		当需要访问某个页的数据时,先从hash表中根据表空间号+页号看是否存在对应的缓冲页。如果有则直接使用;如果没有,就从free链表中选出一个空闲的缓冲页,然后把磁盘中对应的页加载到该缓冲页的位置

93.InnoDB如何管理Page页
	BP的底层采用链表数据结构管理page,在innodb访问表记录和索引时会在page页中缓存,以后使用可以减少磁盘io操作。page有三种状态:
		free page	空闲page,未被使用
		clean page	被使用page,数据没有被修改过
		dirty page	脏页,被使用page且数据被修改过
	有三个链表来管理这三种page:
		free list		空闲缓冲区,管理free page。free链表把所有空闲的缓冲页对应的控制块作为节点放入链表中,其中基节点不记录缓存页,而是放了链表的首尾结点的地址和链表当前节点个数
			!!@@202307311.png_953_493_1@@!!
		flush list	表示需要刷新到磁盘的缓冲区,管理dirty page,按修改时间排序。innodb每次修改不是立刻把修改刷新到磁盘上,而是在未来的某个时间点刷新。方式被修改过的缓冲页都会作为节点加入该链表
			!!@@202307312.png_1103_425_1@@!!
		lru list		表示正在使用的缓冲区,管理clean page和dirty page,缓冲区以midpoint为基点,前面称为new列表区,存放经常访问的数据,占63%,后面的称为old列表区,存放使用较少的数据,占37%
			!!@@202307313.png_955_288_1@@!!
		
94.	为什么写缓冲区仅适用于非唯一普通索引页
	Change Buffer:写缓冲区,是针对二级索引(辅助索引)页的更新优化措施。在进行dml操作时,如果请求的辅助索引没有在缓冲池中,并不会立刻将磁盘页加载到缓冲池,而是在CB记录缓冲变更,等未来数据被读取时再将数据合并恢复到BP中
		!!@@202307314.png_1088_617_1@@!!
	CB用于存储SQL变更操作,包括增删改等语句;每个变更操作都有其对应的数据页,且该数据页未加载到缓存中;当CB中变更操作对应的数据页加载到缓冲区中,innodb会把变更merge到数据页上,innodb也会定期加载CB中操作的数据页到缓存并merger
		!!@@202307315.png_1183_682_1@@!!
	如果索引设置了唯一性,在进行修改时,innodb必须要做唯一性校验,因此必须查询磁盘做一次io操作,会直接将记录查询到BP中并修改,不会在CB操作

95.MySQL为什么改进lru算法
	传统的lru算法(last recently used)就是末尾淘汰法,最近使用或加入的数据会放到链表头,释放空间从链表尾部淘汰
		!!@@202307317.png_1142_467_1@@!!
	优点:所有最近使用的数据都在表头,未使用的都在表尾,保证热数据最快获取到
	缺点:如果发生全表扫描(比如没有建立合适的索引或者查询使用select *),则有很大可能将真正的热数据淘汰掉
			由于MySQL中存在预读机制,很多预读页都会被放到LRU链表头,如果这些预读页没有被用到,会导致很多尾部的缓冲页被淘汰
	改进型LRU算法:将链表分为new和old两部分,加入元素时不是从头而是从midpoint插入,如果数据很快被访问那么page就会向new列表头移动,反之逐步向old尾部移动等待淘汰
		!!@@202307318.png_1228_572_1@@!!

96.使用索引一定可以提高效率吗
	一般索引不要超过5个字段
	在经常需要搜索,主键,用在连接,需要根据范围搜索,需要排序,使用where字句的列上创建索引
	!!@@202308011.png_1212_753_1@@!!

97.Page页的结构
	page是innodb存储的最基本构建,也是innodb磁盘管理的最小单位,与数据库相关的所有内容都存储在page中
	Page分为 数据页(B+tree Node) Undo页(Undo Log Page) 系统页(System Page) 事务数据页(Transaction System Page)等
		!!@@202308131.png_872_499_1@@!!  !!@@202308132.png_1141_357_1@@!!
	页结构整体上可以分为三大部分,分别为通用部分(文件头、文件尾)、存储记录空间、索引部分。
	1)通用部分
		通用部分:主要指文件头和文件尾,将页的内容进行封装,通过文件头和文件尾校验的CheckSum方式来确保页的传输是完整的。
		其中比较重要的是在文件头中的FIL_PAGE_PREV和FIL_PAGE_NEXT字段,通过这两个字段,我们可以找到该页的上一页和下一页,实际上所有页通过两个字段可以形成一条双向链表
	2)记录部分
		页的主要作用是存储记录,所以“最小和最大记录"和“用户记录"部分占了页结构的主要空间。另外空闲空间是个灵活的部分,当有新的记录插入时,会从空闲空间中进行分配用于存储新记录
	3)数据目录部分(Page Directory)
		数据页中行记录按照主键值由小到大顺序串联成一个单链表(页中记录是以单向链表的形式进行存储的),且单链表的链表头为最小记录,链表尾为最大记录。并且为了更快速地定位到指定的行记录,通过Page Directory实现目录的功能,借助Page Directory使用二分法快速找到需要查找的行记录。

98.聚簇索引和非聚簇索引
	他们的区别:叶子节点是否存放一整行记录
		聚簇索引:将数据存储和索引放在一起,索引结构的页节点保存了行数据
		非聚簇索引:分开存储,索引结构的叶子节点指向数据的位置
	InnoDB主键使用的是聚簇索引,MyIlSAM不管是主键索引,还是二级索引使用的都是非聚簇索引。

	聚簇索引(聚集索引)
		聚簇索引是一种数据存储方式,InnoDB的聚簇索引就是按照主键顺序构建B+Tree结构。B+Tree的叶子节点就是行记录,行记录和主键值紧凑地存储在一起。这也意味着InnoDB的主键索引就是数据表本身,它按主键顺序存放了整张表的数据,占用的空间就是整个表数据量的大小。通常说的主键索引就是聚集索引。. lnnoDB的表要求必须要有聚簇索引:
			如果表定义了主键,则主键索引就是聚簇索引
			如果表没有定义主键,则第一个非家unique列作为聚簇索引
			否则InnoDB会重建一个隐藏的row-id作为聚簇索引
	辅助索引
		InnoDB辅助索引,也叫作二级索引,是根据索引列构建B+Tree结构。但在B+Tree 的叶子节点中只存了索引列和主键的信息。二级索引占用的空间会比聚簇索引小很多,通常创建辅助索引就是为了提升查询效率。一个表InnoDB只能创建一个聚簇索引,但可以创建多个辅助索引。
		!!@@202308133.png_1347_887_1@@!!

	非聚簇索引
		与InnoDB表存储不同,MyISM使用的是非聚簇索引,非聚簇索引的两棵B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。
		表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
		!!@@202308134.png_1371_929_1@@!!

	聚簇索引的优点
		1.当你需要取出一定范围内的数据时,用聚簇索引也比用非聚簇索引好。
		2.当通过聚簇索引查找目标数据时理论上比非聚簇索引要快,因为非聚簇索引定位到对应主键时还要多一次目标记录寻址,即多一次I/O。
		3.使用覆盖索引扫描的查询可以直接使用页节点中的主键值。
	聚簇索引的缺点
		1.插入速度严重依赖于插入顺序。(所以一般定义一个自增的主键 )
		2.更新主键的代价很高,因为将会导致被更新的行移动。(所以一般不会更新主键)
		3.二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。

99.索引有哪几种类型
	1)普通索引	这是最基本的索引类型,基于普通字段建立的索引,没有任何限制。
		CREATE INDEX<索引的名字>ONtablename(字段名);
		ALTER TABLE tablename ADD INDEX [索引的名字](字段名);
		CREATE TABLE tablename [...],INDEX[索引的名字](字段名));
	2)唯一索引	与"普通索引"类似,不同的就是:索引字段的值必须唯一,但允许有空值。查询效率要比普通索引更高一些
		CREATE UNIQUE INDEX<索引的名字>ON tablename(字段名);
		ALTER TABLE tablename ADD UNIQUE INDEX[索引的名字](字段名);CREATE TABLE tablename ( [...],UNIQUE[索引的名字](字段名);
	3)主键索引	它是一种特殊的唯一索引,不允许有空值。在创建或修改表时追加主键约束即可,每个表只能有一个主键。
		CREATE TABLE tablename ( [...],PRIMARY KEY (字段名));
		ALTER TABLE tablename ADD PRIMARY KEY (字段名);
	4)复合索引	用户可以在多个列上建立索引,这种索引叫做组复合索引(组合索引)。复合索引可以代替多个单一索引,相比多个单一索引复合索引所需的开销更小。一般推荐复合索引代替多个单一索引
		CREATE INDEX<索引的名字ON tablename(字段名1,字段名2...);
		ALTER TABLE tablename ADD INDEX[索引的名字](字段名1,字段名2...);
		CREATETABLE tablename ( [...],INDEX[索引的名字](字段名1,字段名2...) );
	   复合索引使用注意事项:
		何时使用复合索引,要根据where条件建索引,注意不要过多使用索引,过多使用会对更新操作效率有很大影响。
		如果表已经建立了(col1,col2),就没有必要再单独建立(col1);如果现在有(col1)索引,如果查询需要col1和col2条件,可以建立(col1,col2)复合索引,对于查询有一定提高
	5)全文索引
		查询操作在数据量比较少时,可以使用like模糊查询,但是对于大量的文本数据检索,效率很低。如果使用全文索引,查询速度会比like快很多倍。
		在MySQL 5.6以前的版本,只有MylSAM存储引擎支持全文索引,从MySQL 5.6开始MylSAM和InnoDB存储引擎均支持。
			CREATE FULLTEXT INDEX<索引的名字>ON tablename(字段名);-
			AL TER TABLE tablename ADD FULLTEXT[索引的名字](字段名);
			CREATE TABLE tablename ( [...],FULLTEXT KEY[索引的名字](字段名);
		全文索引方式有自然语言检索IN NATURAL LANGUAGE MODE和布尔检索IN BOOLEAN MODE两种,和常用的like模糊查询不同,全文索引有自己的语法格式,使用match和 against 关键字,比如
			SELECT * FROM users3 WHERE MATCH(NAME) AGAINST( 'aabb ' );
			--*表示通配符,只能在词的后面
			SELECT * FROM users3 WHERE MATCH(NAME) AGAINST( 'aa*’ IN BOOLEAN MODE);
		全文索引使用注意事项:
			全文索引必须在字符串、文本字段上建立。
			全文索引字段值必须在最小字符和最大字符之间的才会有效。(innodb: 3-84; myisam: 4-84)

100.最佳左前缀法则
	如果创建的是联合索引,就要遵循该法则。使用索引时,,where后面的条件需要从索引的最左前列开始使用,并且不能跳过索引中的列使用。
	比如索引为index(name, age, level),对于查询语句where后,如果字段顺序就是如此,那么会走到索引;如果字段为age,level,是用不到索引的;如果字段是age,name,level,这样也是可以走到索引,虽然不符合但是MySQL会帮助优化
	
	最佳左前缀底层原理:MySQL创建联合索引的规则是:首先会对联合索引最左边的字段进行排序(例子中是user_name),在第一个字段的基础之上再对第二个字段进行排序(例子中是user_age ) .
		!!@@202308135.png_1270_587_1@@!!

101.什么是索引下推
	索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询。
	比如辅助索引是index(user_name, user_age),对于查询语句 SELECT * FROM users WHERE user_name LIKE‘张%’AND user_age = 10,根据最左前缀法则,该语句搜索时只能匹配到name的字段,然后逐个回主键索引找出对应记录,再比对age字段
	而如果开启了索引下推,在索引遍历的过程中,就会对索引中包含的字段先做判断,过滤掉不符合的记录,减少回表

	总结:
	如果没有索引下推优化(或称ICP优化),当进行索引查询时,首先根据索引来查找记录,然后再根据where条件来过滤记录;
	在支持ICP优化后,MySQL会在取出索引的同时,判断是否可以进行where条件过滤再进行索引查询,也就是说提前执行where的部分过滤操作,在某些场景下,可以大大减少回表次数,从而提升整体性能。

101.什么是自适应哈希索引
	自适应Hash索引(Adatptive Hash Index,内部简称AHI)是InnoDB的三大特性之一,还有两个是Buffer Pool简称BP、双写缓冲区(Doublewrite Buffer) 。
		1、自适应即我们不需要自己处理,当InnoDB引擎根据查询统计发现某一查询满足hash索引的数据结构特点,就会给其建立一个hash索引;
		2、hash索引底层的数据结构是散列表(Hash表),其数据特点就是比较适合在内存中使用,自适应Hash索引存在于InnoDB架构中的缓存中(不存在于磁盘架构中),见下面的InnoDB架构图。
		3、自适应hash索引只适合搜索等值的查询,如select * from table where index_col='xxx',而对于其他查找类型,如范围查找,是不能使用的;
			!!@@202308136.png_1317_950_1@@!!
	
		Adaptive Hash Index是针对B+树Search Path的优化,因此所有会涉及到Search Path的操作,均可使用此Hash索引进行优化.
		根据索引键值(前缀)快速定位到叶子节点满足条件记录的Offset,减少了B+树Search Path的代价,将B+树从Root节点至Leaf节点的路径定位,优化为Hash Index的快速查询。
			InnoDB的自适应Hash索引是默认开启的,可以通过配置下面的参数设置进行关闭:innodb_adaptive_hash_index = off
			自适应Hash索引使用分片进行实现的,分片数可以使用配置参数设置:innodb_adaptive_hash_index_parts = 8
		!!@@202308137.png_1319_677_1@@!!

102.为什么like以%开头索引会失效
	like查询范围,%如果出现在左边,索引就会失效,在右边则仍然走索引。因为索引是以字段值的首字母排序的,%在右边仍然可以匹配左侧的固定字段
		EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom% ' ;  -- 失效
		EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom ' ;  -- 失效
		EXPLAIN SELECK * FROM users WHERE user_name LIKE 'tom% ' ;  -- 生效

	为了解决like的索引失效,可以使用覆盖索引,即明确写出要查询的字段,且该字段在索引中(最左要能匹配到)
		EXPLAIN SELECT user_name FROM users WHERE user_name LIKE '%jack%';
		EXPLAIN SELECT user_name,user_age, user_level FROM users WHERE user_name LIKE '%jack%' ;	-- 通过使用覆盖索引type = index,并且extra = Using index,从全表扫描变成了全索引扫描.
	特别的,如果上述表只有id,user_name,user_age, user_level四个字段,那么在索引index(user_name,user_age, user_leve)时使用SELECT *也是可以走索引的,因为非聚簇索引本身就是索引字段+主键共同构成的索引,天然包含了所有字段

103.自增还是UUID,数据库主键的类型如何选择
	auto_increment的优点:
		1.字段长度较uuid小很多,可以是bigint甚至是int类型,这对检索的性能会有所影响。
		2在写的方面,因为是自增的,所以主键是趋势自增的,也就是说新增的数据永远在后面,这点对于性能有很大的提升。
		3.数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利。
		4.数字型,占用空间小,易排序,在程序中传递也方便。
	auto_increment的缺点:
		1.由于是自增,很容易通过网络爬虫知晓当前系统的业务量。
		2.高并发的情况下,竟争自增锁会降低数据库的吞吐能力。
		3.数据迁移或分库分表场景下,自增方式坏再适用。
	UUID的优点:
		1.不会冲突。进行数据拆分、合并存储的时候,能保证主键全局的唯一性
		2.可以在应用层生成,提高数据库吞吐能力
	UUID的缺点:
		1.影响插入速度,并且造成硬盘使用率低。与自增相比,最大的缺陷就是随机io
		2.字符串类型相比整数类型肯定更消耗空间,而且会比整数类型操作慢。

	自增的主键的值是顺序的,所以InnoDB把每一条记录都存储在一条记录的后面
		当达到页面的最大填充因子时候(InnoDB 默认的最大填充因子是页大小的15/16,会留出1/16的空间留作以后的修改),下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费。
		新插入的行一定会在原有的最大数据行下一行,MySQL定位和寻址很快,不会为计算新行的位置而做出额外的消耗。减少了页分裂和碎片的产生。
	插入UUID:新的记录可能会插入之前记录的中间,因此需要移动之前的记录,被写满已经刷新到磁盘上的页可能会被重新读取
		因为uid相对顺序的自增id来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以innodb无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:
			1.写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb 在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机IO。
			⒉.因为写入是乱序的,innodb不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上。
			3.由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片。
			4.在把随机值(uuid和雪花 id)载入到聚簇索引(InnoDB默认的索引类型)似后,有时候会需要做一次OPTIMEIZE TABLE来重建表并优化页的填充,这将又需要一定的时间消耗。
	结论:使用InnoDB应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行。如果是分库分表场景下,分布式主键ID的生成方案优先选择雪花算法生成全局唯一主键(雪花算法生成的主键在一定程度上是有序的)。		

104.Innodb与MyISAM的区别
	InnoDB和MylSAM是使用MySQL时最常用的两种引擎类型,我们重点来看下两者区别。
	事务和外键
		lnnoDB支持事务和外键,具有安全性和完整性,适合大量insert或update操作
		MylSAM不支持事务和外键,它提供高速存储和检索,适合大量的select查询操作
	锁机制
		lnnoDB支持行级锁,锁定指定记录。基于索引来加锁实现。
		MyISAM支持表级锁,锁定整张表。
	索引结构
		InnoDB使用聚集索引(聚簇索引),索引和记录在一起存储,既缓存索引,也缓存记录。
		MyISAM使用非聚集索引(非聚簇索引),索引和记录分开。
	并发处理能力
		MylSAM使用表锁,会导致写操作并发率低,读之间并不阻塞,读写阻塞。
		InnoDB读写阻塞可以与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发
	存储文件
		InnoDB表对应两个文件,一个.frm表结构文件,一个.ibd数据文件。InnoDB表最大支持64TB;
		MyISAM表对应三个文件,一个.frm表结构文件,一个MYD表数据文件,一个.MYI索引文件。从MySQL5.0开始默认限制是256TB。

	MylSAM适用场景:不需要事务支持(不支持);并发相对较低(锁定机制问题);数据修改相对较少,以读为主;数据一致性要求不高
	lnnoDB适用场景:需要事务支持(具有较好的事务特性);行级锁定对高并发有很好的适应能力;数据更新较为频繁的场景;数据一致性要求较高;硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,减少磁盘IO

105.B树和B+树的区别
	B-Tree是一种平衡的多路查找树,B树允许一个节点存放多个数据.这样可以在尽可能减少树的深度的同时,存放更多的数据(把瘦高的树变的矮胖).
	B-Tree中所有节点的子树个数的最大值称为B-Tree的阶,用m表示。一颗m阶的B树,如果不为空,就必须满足以下条件.
		1.每个节点最多拥有m-1个关键字(根节点除外),也就是m个子树
		2.根节点至少有两个子树(可以没有子树,有就必须是两个)
		3.分支节点至少有(m/2)颗子树(除去根节点和叶子节点其他都是分支节点)
		4.所有叶子节点都在同一层,并且以升序排序
		!!@@202308151.png_1311_434_1@@!!
	B-Tree的查找操作
		B-Tree的每个节点的元素可以视为一次I/O读取,树的高度表示最多的I/O次数,在相同数量的总元素个数下,每个节点的元素个数越多,高度越低,查淘所需的I/O次数越少.
	B-Tree总结
		优点:B树可以在内部节点存储键值和相关记录数据,因此把频繁访问的数据放在靠近根节点的位置将大大提高热点数据的查询效率。
		缺点: B树中每个节点不仅包含数据的key值,还有data数据.所以当data数据较大时,会导致每个节点存储的key值减少,并且导致B树的层数变高.增加查询时的IO次数.
		使用场景:B树主要应用于文件系统以及部分数据库索引,如MongoDB,大部分关系型数据库索引则是使用B+树实现
	
	B+ Tree是在B-Tree基础上的一种优化,使其更适合实现存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。B+Tree的特征:
		非叶子节点只存储键值信息.
		所有叶子节点之间都有一个链指针
		数据记录都存放在叶子节点中
		!!@@202308152.png_1328_518_1@@!!
	B+Tree的优势
		1.B+Tree是B Tree的变种,BTree能解决的问题,B+Tree也能够解决(降低树的高度,增大节点存储数据量)
		2.B+Tree扫库和扫表能力更强,如果我们要根据索引去进行数据表的扫插,对BTree进行扫描,需要把整棵树遍历一遍,而B+Tree只需要遍历他的所有叶子节点即可(叶子节点之间有引用)
		3.B+Tree磁盘读写能力更强,他的根节点和支节点不保存数据区,所有根节点和支节点同样大小的情况下,保存的关键字要比B Tree要多。而叶子节点不保存子节点引用。所以,B+Tree读写一次磁盘加载的关键字比BTree更多。
		4.B+Tree排序能力更强,如上面的图中可以看出,B+Tree天然具有排序功能。
		5.B+Tree查询效率更加稳定,每次查询数据,查询IO次数一定是稳定的。当然这个每个人的理解都不同,因为在B Tree如果根节点命中直接返回,确实效率更高

106.一个B+树中大概能存放多少条索引记录
	MySQL设计者将一个B+Tree的节点的大小设置为等于一个页.(这样做的目的是每个节点只需要一次I/O就可以完全载入), InnoDB的一个页的大小是16KB,所以每个节点的大小也是16KB,并且B+Tree的根节点是保存在内存中的,子节点才是存储在磁盘上.
		!!@@202308153.png_1335_422_1@@!!
	假设一个B+树高为2,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:根节点指针数*单个叶子节点记录行数.
		计算根节点指针数:假设表的主键为INT类型,占用的就是4个字节,或者是BIGINT占用8个字节,指针大小为6个字节,那么一个页(就是B+Tree中的一个节点) ,大概可以存储:16384B/(4B+6B)= 1638,一个节点最多可以存储1638个索引指针.
		计算每个叶子节点的记录数:我们假设一行记录的数据大小为1k,那么一页就可以存储16行数据,16KB/1KB=16.
		一颗高度为2的B+Tree可以存放的记录数为:1638*16=26208条数据记录,同样的原理可以推算出一个高度3的B+Tree可以存放: 1638 * 1638 * 16= 42928704条这样的记录.
		所以InnoDB中的B+Tree高度一般为1-3层,就可以满足千万级别的数据存储,在查找数据时一次页的查找代表一次IO,所以通过主键索引查询通常只需要1-3次IO操作即可查找到数据。

107.explain有哪些主要字段
	explain各个字段的解释:
		id	查询的序号,没什么实际用处
		select_type	查询类型(如 SIMPLE)
		table	查询的表
		partitions	分区情况
		type	连接类型
		possible_keys		可能会用到的索引
		key		实际使用的索引
		key_len	使用的索引长度。如果使用的是复合索引这个字段比较重要
		ref		索引的哪一列被用到
		rows	表示MySQL想要找到需要的数据,读取的行数
		filtered		表示返回的结果行数占需要读取的行数的百分比
		Extra 	额外字段
		!!@@202308154.png_860_145_1@@!!

108.type有哪些常见的值
	type是连接类型,表示用什么样的方式来获取数据。完整的连接类型比较多,按照从最佳类型到最坏类型进行排序:
		system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery >index_subquery > range > index > ALL
	简化之后,我们可以只关注以下几种
		system > const > eq_ref > ref > range > index >ALL
	一般来说.需要保证查询至少达到range级别,最好能到ref,否则就要就行SQL的优化调整
		system		不进行磁盘IO,查询系统表,仅仅返回一条数据。这个是比较少见的特例
		const	查找主键索引,最多返回1条或0条数据.属于精确查找
		eq_ref		查找唯一性索引,返回数据最多一条,属于精确查找
		ref		查找非唯一性索引,返回匹配某一条件的多条数据,属于精确查找,数据返回可能是多条.
		range	查找某个索引的部分索引,只检索给定范围的行,属于范围查找.比如: >、<.in 、between
		index	查找所有索引树,比ALL快一些,因为索引文件要比数据文件小。index只是指使用了索引但是并没有通过索引过滤。在此说明我们使用的查找条件索引没有起到作用,是一定需要优化的
		ALL	不使用任何索引,直接进行全表扫描

109.Extra有哪些主要指标
	Extra是EXPLAIN输出中另外一个很重要的列,该列显示MySQL在查询过程中的一些详细信息
	Using filesort		MySQL中无法利用索引完成的排序操作称为“文件排序”
		EXPLAIN SELECT* FROM users ORDER BY age;		-- Using filesort,说明age这个排序字段没有加索引
	Using index		表示直接访问索引就能够获取到所需要的数据(覆盖索引),不需要通过索引回表
		EXPLAIN SELECT uid ,uname FROM users WHERE uname='lisa' ;		-- Using index,如果给表加上uname的索引,说明此条件用到了索引
	Using index condition		搜索条件中虽然出现了索引列,但是有部分条件无法使用索引,会根据能用索引的条件先搜索一遍再匹配无法使用索引的条件。
	Using join buffer		使用了连接缓存,会显示join连接查询时,MySQL选择的查询算法
		EXPLAIN SELECT * FROM users u1 LEFTJ0IN (SELECT* FROM users WHERE age = 1) u2 ON u1.age = u2.age;		-- Using join buffer,age没有索引,连接查询使用到了连接缓存
	Using temporary		表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询	
		EXPLAIN SELECT COUNT(*) , uname FROM users GROUP BY uname;		-- Using temporary,分组查询,使用到了临时表
	Using where		意味着全表扫描或者在查找使用索引的情况下,但是还有查询条件不在索引字段当中
		EXPLAIN SELECT * FROM users WHERE age=10;		-- Using where,说明age这个查询字段没有加索引

110.如何进行分页查询优化
	分页查询:SELECT * FROM表名LIMIT 偏移量,记录数
		如果偏移量固定,在查询记录时,返回记录量低于100条,查询时间差距不大。随着查询记录量越大,所花费的时间也会越来越多。
		如果查询偏移量变化,返回记录数固定,偏移量超过100后就开始随着偏移量增大,查询时间急剧的增加。(这种分页查询机制,每次都会从数据库第一条记录开始扫描,越往后查询越慢,而且查询的数据越多,也会拖慢总查询速度。)
	分页优化方案(仅针对id自增连续的才有效):
		优化1:通过索引进行分页。直接进行limit操作会产生全表扫描,速度很慢. Limit限制的是从结果集的M位置处取出N条输出,其余抛弃.假设ID是连续递增的,我们根据查询的页数和查询的记录数可以算出查询的id的范围,然后配合limit使用
				EXPLAIN SELECT * FROM user WHERE id >= 100001 LIMIT 100;
		优化2:利用子查询优化。首先定位偏移位置的id
				SELECT id FROM user_contacts LIMIT 100000,1;
			根据获取到的id值向后查询.
				EXPLAIN SELECT* FROM user_contacts WHERE id >= (SELECT id FROM user_contacts LIMIT 100000,1)LIMIT 100;
			原因:使用了id做主键比较(id>=),并且子查询使用了覆盖索引进行优化。

111.如何做慢查询优化
  MySQL慢查询的相关参数解释:
	slow_query_log:是否开启慢查询日志,ON(1)表示开启,OFF(0)表示关闭。
	slow-query-log-file:新版(5.6及以上版本)MySQL数据库慢查询日志存储路径。
	long_query_time:慢查询阈值,当查询时间多于设定的阈值时,记录日志。默认为10S
  默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的,可以通过设置slow_query_log的值来开启
	!!@@202310011.png_716_358_1@@!!
  使用set global slow_query_log=1开启了慢查询日志只对当前数据库生效,MySQL重启后则会失效。如果要永久生效,就必须修改配置文件my.cnf(其它系统变量也是如此)
	!!@@202310012.png_623_446_1@@!!
  如果使用命令set global long_query_time=1修改了变量long_query_time后,需要重新连接或新开一个会话才能看到修改值。
	!!@@202310021.png_1028_517_1@@!!
  log_output参数是指定日志的存储方式。log_output='FILE'表示将日志存入文件,默认值是'FILE'。log_output='TABLE'表示将日志存入数据库,这样日志信息就会被写入到mysql.slow_log表中。
	!!@@202310022.png_1006_366_1@@!!
  
  得到慢查询日志后,需要去分析。日志里记录了如下内容,可以看到有时间戳,用户,查询时长及具体的SQL等信息
	!!@@202310024.png_1011_472_1@@!!
  一般查询的慢主要两个方面:
	等待时间长
		锁表导致查询一直处于等待状态,后续我们从MySQL锁的机制去分析SQL执行的原理(高并发下)
	执行时间长
		查询语句写的烂;索引失效;关联查询太多join;服务器调优及各个参数的设置
  优化方式:
	1.优先选择优化高并发执行的SQL,因为高并发的SQL发生问题带来后果更严重
		比如两个SQL,一个是一小时执行10000次,每次需要20个IO;另一个SQL一小时执行10次,每次20000个IO,那么第一种就属于高并发,更需要优化,同时也更好优化
	2.定位优化对象的性能瓶颈(在优化之前了解性能瓶颈在哪)
		IO(数据访问消耗的了太多的时间,查看是否正确使用了索引);CPU(数据运算花费了太多时间,数据的运算分组排序是不是有问题);网络带宽(加大网络带宽)
	3.明确优化目标
		了解最好的情况消耗的资源,最差情况下消耗的资源,优化的结果只有一个:给用户一个好的体验
	4.从explain执行计划入手
	5.用小的结果集驱动大的结果集,即用小表驱动大表
	6.尽可能在索引中完成排序
		排序操作用的比较多,order by后面的字段如果在索引中,索引本来就是排好序的,所以速度很快,没有索引的话,就需要从表中拿数据,在内存中进行排序,如果内存空间不够还会发生落盘操作
	7.只获取自己需要的列
		不要使用select * ,select * 很可能不走索引,而且数据量过大
	8.只使用最有效的过滤条件
		误区 where后面的条件越多越好,但实际上是应该用最短的路径访问到数据
	9.尽可能避免复杂的join和子查询
		每条SQL的JOIN操作建议不要超过三张表。将复杂的SQL,拆分成多个小的SQL 单个表执行,获取的结果在程序中进行封装如果join占用的资源比较多,会导致其他进程等待时间变长
	10.合理设计并利用索引
		如何判定是否需要创建索引?
			1.较为频繁的作为查询条件的字段应该创建索引.
			⒉唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件。(唯一性太差的字段主要是指哪些呢?如状态字段,类型字段等等这些字段中的数据可能总共就是那么几个几十个数值重复使用)(当一条Query所返回的数据超过了全表的15%的时候,就不应该再使用索引扫描来完成这个Query了) .
			3.更新非常频繁的字段不适合创建索引。(因为索引中的字段被更新的时候,不仅仅需要更新表中的数据,同时还要更新索引数据,以确保索引信息是准确的).
			4.不会出现在WHERE子句中的字段不该创建索引.
		如何选择合适索引?
			1.对于单键索引,尽量选择针对当前Query过滤性更好的索引.
			2选择联合索引时,当前Query中过滤性最好的字段在索引字段顺序中排列要靠前。
			3.选择联合索引时,尽量索引字段出现在w中比较多的索引.
	
112.Hash索引有哪些优缺点
  Hsah索引的优点
	因为索引自身只需要存储对应的Hash值,所以索引结构非常紧凑,只需要做等值比较查询,而不包含排序或范围查询的需求,都适合使用哈希索引.
	没有哈希冲突的情况下,等值查询访问哈希索引的数据非常快.(如果发生Hash冲突,存储引擎必须遍历链表中的所有行指针,逐行进行比较,直到找到所有符合条件的行).
  Hash索引的缺点
	哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。
	哈希索引只支持等值比较查询。不支持任何范围查询和部分索引列匹配查找。
	哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。

113.Innodb内存相关的参数优化
  查看缓冲池的大小:show variables like '%innodb_buffer_pool_size%';	默认是128M。
  可以手动动态修改大小,且无需重启服务器:SET GLOBAL innodb_buffer_pool_size = 268435456;      -- 修改成256
  查看缓冲池修改进度:SHOW STATUS WHERE Variable_name='InnoDB_buffer_pool_resize_status'
	!!@@202310031.png_1037_462_1@@!!

  如何查看缓冲池的大小设置是否合理,可以使用以下公式计算命中率,只要低于90%,就可以考虑增加大小:
	!!@@202310033.png_816_420_1@@!!  !!@@202310034.png_608_495_1@@!!

  查看Page页的大小(默认16KB),innodb_page_size只能在初始化MySQL实例之前配置,不能在之后修改。如果没有指定值,则使用默认页面大小初始化实例。
	show variables like '%innodb_page_size%' ;
  Page管理状态相关参数查看:     show global status like '%innodb_buffer_pool_pages%';
	!!@@202310037.png_901_557_1@@!!

114.Innodb日志相关参数优化
  innodb_log_buffer_size  日志缓冲区的大小。一般默认值16MB是够用的,但如果事务之中含有blog/text等大字段,这个缓冲区会被很快填满,会引起额外的IO负载。配置更大的日志缓冲区,可以有效的提高MySQL的效率
	!!@@202310041.png_560_167_1@@!!
  innodb_log_files_in_group   日志组文件个数。日志组根据需要来创建。而日志组的成员则需要至少2个,实现循环写入并作为冗余策略。
	!!@@202310042.png_596_165_1@@!!
  innodb_log_file_size   日志文件大小。参数innodb_log_file_size用于设定MySQL日志组中每个日志文件的大小(默认48M)。此参数是一个全局的静态参数,不能动态修改。
						二进制日志文件大小(innodb_log_file_size*innodb_log_files_in_group)不能超过512GB,所以单个日志文件的大小不能超过256G
	!!@@202310043.png_566_165_1@@!!


   日志文件大小的设置对性能的影响
	设置过小
		1.参数innodb_log_file_size设置太小,就会导致MySQL的日志文件( redo log)频繁切换,频繁的触发数据库的检查点(Checkpoint),导致刷新脏页到磁盘的次数增加。从而影响IO性能。
		2.处理大事务时,将所有的日志文件写满了,事务内容还没有写完,这样就会导致日志不能切换
	设置过大
		参数innodb_log_file_size如果设置太大,虽然可以提升IO性能,但是当MySQL由于意外宕机时,二进制日志很大,那么恢复的时间必然很长。而且这个恢复时间往往不可控,受多方面因素影响。
  如何设置合适的日志文件大小
	根据实际生产场景的优化经验,一般是计算一段时间内生成的事务日志(redo log)的大小,而MySQL的日志文件的大小最少应该承载一个小时的业务日志量(官网文档中有说明)。
	想要估计一下InnoDB redo log的大小,需要抓取一段时间内Log SequenceNumber(日志顺序号)的数据,来计算一小时内产生的日志大小.
		Log sequence number		自系统修改开始,就不断的生成redo日志。为了记录一共生成了多少日志,于是mysql设计了全局变量logsequence number,简称lsn,但不是从0开始,是从8704字节开始。
  		!!@@202310045.png_592_476_1@@!!
	有了一分钟的日志量,据此推算一小时内的日志量。太大的缓冲池或非常不正常的业务负载可能会计算出非常大(或非常小)的日志大小,需要根据判断和经验。这个计算方法是一个很好的参考标准。
		!!@@202310046.png_659_337_1@@!!

115.InnoDB IO线程相关参数优化
  数据库属于lO密集型的应用程序,其主要职责就是数据的管理及存储工作。从内存中读取一个数据库数据的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别。要优化数据库,IO操作是必须要优化的,尽可能将磁盘IO转化为内存lO。
	1)参数: query_cache_size&have_query_cache  MySQL查询缓存会保存查询返回的完整结果。当查询命中该缓存,会立刻返回结果,跳过了解析,优化和执行阶段。查询缓存会跟踪查询中涉及的每个表,如果这些表发生变化,那么和这个表相关的所有缓存都将失效。
	1.查看查询缓存是否开启
		!!@@202310047.png_530_404_1@@!!
	2.开启缓存,在my.ini中添加下面一行参数
		!!@@202310048.png_650_189_1@@!!
	3.测试能否缓存查询
		!!@@202310049.png_937_663_1@@!!
	优化建议:Query Cache的使用需要多个参数配合,其中最为关键的是query_cache_size和query_cache_type,前者设置用于缓存ResultSet 的内存大小,后者设置在何场景下使用Query Cacheo
		MySQL数据库数据变化相对不多,query_cache_size一般设置为256MB比较合适,也可以通过计算Query Cache的命中率来进行调整:( Qcache_hits/ ( Qcache_hits + Qcache_inserts )*100))
	2)参数:  innodb_max_dirty_pages_pct   该参数是InnoDB存储引擎用来控制buffer pool中脏页的百分比,当脏页数量占比超过这个参数设置的值时,InnoDB会启动刷脏页的操作。
		!!@@2023100410.png_842_181_1@@!!
	优化建议:该参数比例值越大,从内存到磁盘的写入操作就会相对减少,所以能够一定程度下减少写入操作的磁盘lO。但是,如果这个比例值过大,当数据库Crash之后重启的时间可能就会很长,因为会有大量的事务数据需要从日志文件恢复出来写入数据文件中.最大不建议超过90,一般重启恢复的数据在超过1GB的话,启动速度就会变慢.
	3)参数: innodb_old_blocks_pct&innodb_old_blocks_time   innodb_old_blocks_pct用来确定LRU链表中old sublist所占比例,默认占用37%
		!!@@2023100411.png_588_167_1@@!!
	innodb_old_blocks_time用来控制old sublist中page的转移策略,新的page页在进入LRU链表中时,会先插入到old sublist的头部,然后page需要在old sublist中停留innodb_old_blocks_time这么久后,下一次对该page的访问才会使其移动到new sublist的头部,默认值1秒.
		!!@@2023100412.png_641_173_1@@!!
	优化建议:在没有大表扫描的情况下,并且数据多为频繁使用的数据时,我们可以增加innodb_old_blocks_pct的值,并且减小innodb_old_blocks_time的值。让数据页能够更快和更多的进入的热点数据区。

116.什么是写失效
  InnoDB的页和操作系统的页大小不一致,InnoDB页大小一般为16K,操作系统页大小为4K,InnoDB的页写入到磁盘时,一个页需要分4次写。
  如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的情况,比如只写了4K,就宕机了,这种情况叫做部分写失效(partial page write),可能会导致数据丢失。

  双写缓冲区Doublewrite Buffer
	为了解决写失效问题,InnoDB实现了double write buffer Files,它位于系统表空间,是一个存储区域。
	在BufferPool的page页刷新到磁盘真正的位置前,会先将数据存在Doublewrite缓冲区。这样在宕机重启时,如果出现数据页损坏,那么在应用redo log之前,需要通过该页的副本来还原该页,然后再进行redo log重做,double write实现了InnoDB引擎数据页的可靠性.
	默认情况下启用双写缓冲区,如果要禁用Doublewrite缓冲区,可以将innodb_doublewrite设置为O。
		!!@@2023100413.png_548_199_1@@!!
	数据双写流程:
		!!@@2023100414.png_842_667_1@@!!

117.什么是行溢出
  表的行格式决定了它的行是如何物理存储的,这反过来又会影响查询和DML操作的性能。如果在单个page页中容纳更多行,查询和索引查找可以更快地工作,缓冲池中所需的内存更少,写入更新时所需的VO更少。
  InnoDB存储引擎支持四种行格式: Redundant、Compact、Dynamic和Compressed,MySQL使用的行格式,默认为 dynamic
	!!@@2023100415.png_628_284_1@@!!
  以Compact行记录格式为例说明行溢出:
	Compact设计目标是高效地存储数据,一个页中存放的行数据越多,其性能就越高。Compact行记录由两部分组成:记录放入额外信息和记录的真实数据.
		!!@@2023100416.png_894_146_1@@!!
	记录额外信息部分
		服务器为了描述一条记录而添加了一些额外信息(元数据信息),这些额外信息分为3类,分别是:变长字段长度列表、NULL值列表和记录头信息.
		变长字段长度列表
			MySQL支持一些变长的数据类型,比如VARCHAR(M)、VARBINARY(M)、各种TEXT类型,各种BLOB类型,这些变长的数据类型占用的存储空间分为两部分:
				1.真正的数据内容		2.占用的字节数
		变长字段的长度是不固定的,所以在存储数据的时候要把这些数据占用的字节数也存起来,读取数据的时候才能根据这个长度列表去读取对应长度的数据。
		在Compact行格式中,把所有变长类型的列的长度都存放在记录的开头部位形成一个列表,按照列的顺序逆序存放,这个列表就是变长字段长度列表。
		NULL值列表
		表中的某些列可能会存储NUL,值,如果把这些NULL值都放到记录的真实数据中会比较浪费空间,所以Compact行格式把这些值为NULL的列存储到NULL值列表中。(如果表中所有列都不允许为NULL,就不存在NULL值列表)
		记录头信息
		记录头信息是由固定的5个字节组成,5个字节也就是40个二进制位,不同的位代表不同的意。
			!!@@2023100417.png_770_478_1@@!!	!!@@2023100418.png_887_591_1@@!!
	记录真实数据部分
		记录的真实数据除了插入的那些列的数据,MySQL会为每个记录默认的添加一些列(也称为隐藏列),具体的列如下:
			!!@@2023100419.png_818_303_1@@!!	!!@@2023100420.png_915_410_1@@!!
	Compact中的行溢出机制
		InnoDB 规定一页至少存储两条记录(B+树特点),如果页中只能存放下一条记录,InnoDB存储引擎会自动将行数据存放到溢出页中。当发生行溢出时,数据页只保存了前768字节的前缀数据,接着是20个字节的偏移量,指向行溢出页.
			!!@@2023100421.png_842_268_1@@!!

118.如何进行join优化
  1.永远用小结果集驱动大结果集(其本质就是减少外层循环的数据数量)
	多表关联查询时,第一个被处理的就是驱动表。在对结果没影响的情况下,优先选择结果集小的那张表作为驱动表
  2.为匹配的条件增加索引(减少内层表的循环匹配次数)
	例如sql: select * from user t1 left join order t2 on t1.id = t2.user_id;   -- user表为驱动表, order表为被驱动表
	join有三种算法:
		simple nested-loop join(SNL,简单的嵌套循环连接):类似于双层for循环,循环外表的数据逐条与内层表所有数据比较。该算法最简单,对内循环无优化
		Index Nested-Loop Join(索引嵌套循环连接):主要为了减少内层表的匹配次数,最大的区别在于用来join的字段已经添加了索引。以前的匹配次数是外层表行数*内层表行数,现在是外层表行数*内层表索引的高度,极大提升了性能
  3.增大join buffer size的大小(一次缓存的数据越多,那么内层包的扫表次数就越少)
		Block Nested-Loop Join(块嵌套循环连接):如果join的字段有索引,那么就会使用上面的算法,如果没有,mysql也不会采用snl,而是加入buffer缓冲区,降低被驱动表的扫描次数
		!!@@2023100422.png_1013_641_1@@!!
  4.减少不必要的字段查询(字段越少,join buffer所缓存的数据就越多

119.索引哪些情况会失效
	1.查询条件包含or,会导致索引失效。
	⒉.隐式类型转换,会导致索引失效,例如age字段类型是int,我们where age =“1”",这样就会触发隐式类型转换
	3. like通配符会导致索引失效,注意:"ABC%”不会失效,会走 range 索引,”%ABC”索引会失效
	4.联合索引,查询时的条件列不是联合索引中的第一个列,索引失效。
	5.对索引字段进行函数运算。
	6.对索引列运算(如,+、-、*、/),索引失效。
	7.索引字段上使用(!=或者<>,not in)时,会导致索引失效。
	8.索引字段上使用is null, is not null,可能导致索引失效。
	9.相 join的两个表的字符编码不同,不能命中索引,会导致笛卡尔积的循环计算
	10. mysql估计使用全表扫描要比使用索引快,则不使用索引。

120.覆盖索引
  覆盖索引是一种避免回表查询的优化策略:只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。
	将被查询的字段建立普通索引或者联合索引,这样的话就可以直接返回索引中的的数据,不需要再通过聚集索引去定位行记录,避免了回表的情况发生。
	EXPLAIN SELECT user_name,user_age,user_level FROM users WHERE user_name = 'tom’ AND user_age = 17;
	!!@@2023100423.png_1055_684_1@@!!

121.MySQL中事务的特性
	在关系型数据库管理系统中,一个逻辑工作单元要成为事务,必须满足这4个特性,即所谓的ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
	1)原子性 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
		InnoDB存储引擎提供了两种事务日志: redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。
		每写一个事务,都会修改Buffer Pool,从而产生相应的Redo/Undo日志。如果要回滚事务,那么就基于undo log来回滚就可以了,把之前对缓存页做的修改都绘回滚了就可以了。如果事务提交之后,redo log刷入磁盘,结果MySQL宕机了,是可以根据redo log恢复事务修改过的缓存数据的。
			!!@@2023100425.png_965_359_1@@!!
	2)一致性 事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
		约束一致性:创建表结构时所指定的外键、唯一索引等约束。
		数据一致性:是一个综合性的规定,因为它是由原子性、持久性、隔离性共同保证的结果,而不是单单依赖于某一种技术。
	3)隔离性 指的是一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他的并发事务是隔离的。
		不考虑隔离性会引发的问题:
			脏读:一个事务读取到了另一个事务修改但未提交的数据。
			不可重复读:一个事务中多次读取同一行记录的结果不一致,后面读取的跟前面读取的结果不一致。
			幻读:一个事务中多次按相同条件查询,结果不一致。后续查询的结果和面前查询结果不同,多了或少了几行记录。
		数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed、Repeatable read ,Serializable ,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
	4)持久性 指的是一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,后续的操作或故障不应该对其有任何影响,不会丢失。
		MySQL事务的持久性保证依赖的日志文件:redo log
			redo log也包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重做日志文件(redo log file),该部分日志是持久的。redo log是物理日志,记录的是数据库中物理页的情况.
			当数据发生修改时,InnoDB不仅会修改Buffer Pool中的数据,也会在redo log buffer记录这次操作;当事务提交时,会对redo log buffer进行刷盘,记录到redo log file中。如果MySQL宕机,重启时可以读取redo log file中的数据,对数据库进行恢复。这样就不需要每次提交事务都实时进行刷脏了。
		!!@@2023100426.png_977_356_1@@!!

  ACID总结
	事务的持久化是为了应对系统崩溃造成的数据丢失
	只有保证了事务的一致性,才能保证执行结果的正确性
	在非并发状态下,事务间天然保证隔离性,因此只需要保证事务的原子性即可保证一致性
	在并发状态下,需要严格保证事务的原子性、隔离性。

122.MySQL的可重复读是怎么实现的
	

123.Repeatable Read解决了幻读问题吗?


124.数据库锁的种类
  MySQL数据库由于其自身架构的特点,存在多种数据存储引擎, MySQL中不同的存储引擎支持不同的钡机制。
	MylSAM和MEMORY存储引擎采用的表级锁,
	InnoDB存储引擎既支持行级锁,也支持表级锁,默认情况下采用行级锁。
	BDB采用的是页面锁,也支持表级锁
  按照数据操作的类型分
	读锁(共享锁)︰针对同一份数据,多个读操作可以同时进行而不会互相影响。
	写锁(排他锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
  按照数据操作的粒度分
	表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
	行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
	页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
  按照操作性能可分为乐观锁和悲观锁
	乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测,如果发现冲突了,则提示错误信息。
	悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,再修改的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观锁范畴。


125.共享锁和排它锁
  行级锁分为共享锁和排他锁两种。
	行锁是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大,但是也会造成死锁,每次加锁和释放锁的开销也会变大。
  使用MySQL行级锁的两个前提
	使用innoDB引擎
	开启事务(隔离级别为Repeatable Read)
  lnnoDB行锁的类型
	共享锁(S)∶当事务对数据加上共享锁后,其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。
	排他锁(X)︰如果事务T对数据A加上排他锁后,则其他事务不能再对数据A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
  加锁的方式
	InnoDB引擎默认更新语句,update,delete,insert都会自动给涉及到的数据加上排他锁, select语句默认不会加任何锁类型,如果要加可以使用下面的方式:
		加共享锁(S) : select * from table_name where ... lock in share mode;
		加排他锁(x): select * from table_name where ... for update;
  锁兼容
	共享锁只能兼容共享锁,不兼容排它锁
	排它锁互斥共享锁和其它排它锁


126.l nnoDB 的行锁是怎么实现的?(视频后面有具体的SQL举例)
  InnoDB行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有3种: Record Lock、Gap Lock 和Next-key Lock。
	 RecordLock锁:锁定单个行记录的锁。(记录锁,RC、RR隔离级别都支持)
	GapLock锁:间隙锁,锁定索引记录间隙,确保索引记录的间隙不变。(范围锁,RR隔离级别支持)
	Next-key Lock锁:记录锁和间隙锁组合,同时锁住数据,并且锁住数据前后范围。(记录锁+范围锁,RR隔离级别支持)
  	注意: InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据, InnoDB才使用行级锁,否则,lnnoDB将使用表锁
  	在RR隔离级别,InnoDB对于记录加锁行为都是先采用Next-Key Lock,但是当SQL操作含有唯一索引时,Innodb会对Next-Key Lock进行优化,降级为RecordLock,仅锁住索引本身而非范围。
  各种操作加锁的特点
	1) select . from语句: InnoDB引擎采用MVCC机制实现非阻塞读,所以对于普通的select语句,InnoDB不加锁
	2) selec ...from lock in share mode语句:追加了共享锁,InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。
	3) select ... from for update语句:追加了排他锁,InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。
	4) update ... where语句: lInnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。
	5) delete ... where语句: InnoDB会使用Next-Key Lock锁进行处理,如果扫描发现唯一索引,可以降级为RecordLock锁。
	6) insert语句: InnoDB会在将要插入的那一行设置一个排他的RecordLock锁。

127.并发事务会产生哪些问题
  事务并发处理可能会带来一些问题,如下:
	更新丢失
		当两个或多个事务更新同一行记录,会产生更新丢失现象。可以分为回滚覆盖和提交覆盖。
		回滚覆盖:一个事务回滚操作,把其他事务已提交的数据给覆盖了。
		提交覆盖:一个事务提交操作,把其他事务已提交的数据给覆盖了。
	脏读	一个事务读取到了另一个事务修改但未提交的数据。
	不可重复读	一个事务中多次读取同一行记录不一致,后面读取的跟前面读取的不一致。
	幻读	一个事务中多次按相同条件查询,结果不一致。后续查询的结果和面前查询结果不同,多了或少了几行记录。
  “更新丢失”、"脏读”、“不可重复读"和“幻读"等并发事务问题,其实都是数据库一致性问题,为了解决这些问题,MySQL数据库是通过事务隔离级别来解决的,数据库系统提供了以下4种事务隔离级别供用户选择。
	!!@@202310051.png_973_261_1@@!!
	读未提交	Read Uncommitted读未提交:解决了回滚覆盖类型的更新丢失,但可能发生脏读现象,也就是可能读取到其他会话中未提交事务修改的数据。
	已提交读	Read Committed读已提交:只能读取到其他会话中已经提交的数据,解决了脏读。但可能发生不可重复读现象,也就是可能在一个事务中两次查询结果不一致。
	可重复读	Repeatable Read可重复读:解决了不可重复读,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上会出现幻读,简单的说幻读指的的当用户读取某一范围的数据行时,另一个事务又在该范围插入了新行,当用户在读取该范围的数据时会发现有新的幻影行。
	可串行化	所有的增删改查串行执行。它通过强制事务排序,解决相互冲突,从而解决幻度的问题。这个级别可能导致大量的超时现象的和锁竞争,效率低下。

  事务隔离级别,针对Innodb引擎,支持事务的功能。像MyISAM引擎没有关系。
	MySQL默认隔离级别:可重复读
	Oracle、SQLServer默认隔离级别:读已提交
	一般使用时,建议采用默认隔离级别,然后存在的一些并发问题,可以通过悲观锁、乐观锁等实现处理。

128.MVCC内部细节
	

129.

130.

131.

132.

133.

134.

135.Mysql线上修改大表结构有哪些风险
	!!@@202310052.png_1026_602_1@@!!

136.count(列名) count(*) count(1)的区别
  执行效果上:
	count(*)包括了所有的列,在统计时不会忽略列值为null的数据。
	 count(1)用1表示代码行,在统计时,不会忽略列值为null的数据。
	 count(列名)在统计时,会忽略列值为空的数据,就是说某个字段的值为null时不统计。
  行效率上:
	InnoDB引擎:count(字段)<count(1) = count(*)
		InnoDB通过遍历最小的可用二级索引来处理select count(*)语句,除非索引或优化器提示指示优化器使用不同的索引。如果二级索引不存在,则通过扫描聚集索引来处理。
		InnoDB已同样的方式处理count(1)和count(*)
	MyISAM引擎:count(字段)<count(1)<= count(*)
		MyIlSAM存储了数据的准确行数,使用count(*)会直接读取该行数,只有当第一列定义为NOT NULL时,count (1),才会执行该操作,所以优先选择count(*)
	count(列名)会遍历整个表,但不同的是,它会先获取列,然后判断是否为空,然后累加,因此count(列名)性能不如前两者。

137.什么时候进行分库分表
	垂直分表:将一个表按照字段分成多表,每个表存储其中一部分字段。
	垂直分库:根据表的业务不同,分别存放在不同的库中,这些库分别部署在不同的服务器.
	水平分库:把一张表的数据按照一定规则,分配到不同的数据库,每一个库只有这张表的部分数据.
	水平分表:把一张表的数据按照一定规则,分配到同一个数据库的多张表中,每个表只有这个表的部分数据

138.mysql的主从复制
  主从复制的用途	实时灾备,用于故障切换·读写分离,提供查询服务;备份,避免影响业务
  主从部署必要条件	主库开启binlog日志(设置log-bin参数);主从server-id不同;从库服务器能连通主库
  主从复制的原理	Mysql中有一种日志叫做 bin日志(二进制日志)。这个日志会记录下所有修改了数据库的SQL语句(insert,update,delete,create/alter/drop table, grant 等等)。主从复制其实就是把主服务器上的bin日志复制到从服务器上执行一遍,这样从服务器上的数据就和主服务器上的数据相同了。

139.mysql执行一条查询语句的过程
	!!@@202310053.png_966_628_1@@!!

140.mysql内部支持缓存查询嘛?
  使用缓存的好处:当MySQL接收到客户端的查询SQL之后,仅仅只需要对其进行相应的权限验证之后,就会通过Query Cache来查找结果,甚至都不需要经过Optimizer模块进行执行计划的分析优化,更不需要发生任何存储引擎的交互.
  mysql5.7支持内部缓存,8.0之后已废弃mysql缓存的限制
	1.mysql基本没有手段灵活的管理缓存失效和生效,尤其对于频繁更新的表
	2.SQL必须完全一致才会导致cache命中
	3.为了节省内存空间,太大的result set不会被cache (< query_cache_limit);
	4.MySQL缓存在分库分表环境下是不起作用的;
	5.执行SQL里有触发器,自定义函数时,MySQL缓存也是不起作用的;
	6.在表的结构或数据发生改变时,基于该表相关cache立即全部失效。
  替代方案
	应用层组织缓存,最简单的是使用redis,ehcached等




141.Redis为什么快
   纯内存访问
  单线程避免上下文切换
  渐进式ReHash、缓存时间戳
	当hash需要扩容时,不是一次性拷贝所有的键值对到新的扩容的hash表中去,而是拆分成多个请求,一次只处理一个下标数据的扩容
	redis的时间戳是自己来维护的,而不是调用系统的函数,减少切换减小开销

142.Redis合适的应用场景
  缓存
  计数器
  分布式会话
  排行榜
  最新列表
  分布式锁
  消息队列

143.Redis6.0之前为什么一直不使用多线程
	1、使用Redis、CPU 不是瓶颈,受制内存、网络
	2、提高Redis,Pipeline(命令批量)每秒100万个请求
	3、单线程,内部维护比较低
	4、如果是多线程(线程切换、加锁\解锁、导致死锁问题)
	5、惰性Rehash(渐进性式的Rehash)
  一般的公司,单线程Redis就够了

144.Redis6.0为什么引入多线程
	只是IO的多线程,内部执行命令仍然是单线程

145.Redis有哪些高级功能
	
  
146.为什么要用Redis

147.Redis相对memcached有哪些优势

148.

149.

150.

151.

152.

153.

154.

155.

156.

157.

158.

159.

160.

161.

162.

163.

164.

165.

166.

167.

168.

169.

170.

171.

172.

173.

174.

175.

176.

177.

178.

179.

180.

181.

182.

183.

184.

185.

186.

187.

188.

189.

190.

191.

192.

193.

194.

195.

196.

197.

198.

199.

200.



















DOWN 返回