20、Netty的原理,为什么选择Netty
1、多路复用I/O与异步I/O的区别
多路复用I/O:
select、poll:在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大
epoll:是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。
通过红黑树和双链表数据结构,并结合回调机制,造就了epoll的高效。
一句话描述就是:三步曲。
第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。红黑树存储连接的socket(每个树节点上的事件挂上事件链表)。
第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。挂载一遍事件,加回调函数,有就绪事件就往链表里面加事件。
第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。查看链表里面有无数据。
eopll的高效原因:
上边我们学习了epoll和select/poll的相关基础,并且比较了它们的区别,发现epoll性能高效,那我们需要思考,epoll高效的原因何在。
先总结一下:epoll通过mmap+红黑树+双链表+回调机制,造就了epoll 的高效。
epoll是通过内核与用户空间mmap同一块内存实现的。mmap将用户空间的一块地址和内核空间的一块地址同时映射到相同的一块物理内存地址(不管是用户空间还是内核空间都是虚拟地址,最终要通过地址映射映射到物理地址),使得这块物理内存对内核和对用户均可见,减少用户态和内核态之间的数据交换。内核可以直接看到epoll监听的句柄,效率高。
红黑树将存储epoll所监听的套接字。上面mmap出来的内存如何保存epoll所监听的套接字,必然也得有一套数据结构,epoll在实现上采用红黑树去存储所有套接字,当添加或者删除一个套接字时(epoll_ctl),都在红黑树上去处理,红黑树本身插入和删除性能比较好,时间复杂度O(logN)。
一个fd被添加到epoll中之后(调用epoll_ctl的epoll_ctl_add模式),内核会为其生成一个对应的epitem结构对象,然后epitem会被添加到红黑树中,红黑树的作用就是在添加、删除、修改fd时,能够快速实现。
添加以及返回事件
通过epoll_ctl函数添加进来的事件fd都会被放在红黑树的某个节点内,所以,重复添加是没有用的。
建立回调+回调的逻辑是将就绪事件fd加入到双向链表中,epoll_wait调用时只需判断链表是否为空即可。
当把事件fd添加进来的时候时候会完成关键的一步,那就是该事件(fd(socket+事件类型))都会与相应的设备(网卡)驱动程序建立回调关系,当相应的事件发生后(O(1)就可判断事件是否发生),就会调用这个回调函数,该回调函数在内核中被称为:ep_poll_callback,这个回调函数的逻辑就是就所把这个事件添加到rdllist这个双向链表中。一旦有事件发生,epoll就会将该事件添加到双向链表中。那么当我们调用epoll_wait时,epoll_wait只需要检查rdlist双向链表中是否有存在注册的事件,效率非常可观。这里也需要将链表中就绪的事件复制到用户态内存中即可。
epoll_wait的工作流程:
epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒。
文件fd状态改变(buffer由不可读变为可读或由不可写变为可写),导致相应fd上的回调函数ep_poll_callback()被调用。
ep_poll_callback将相应fd对应epitem加入rdlist,导致rdlist不空,进程被唤醒,epoll_wait得以继续执行。
ep_events_transfer函数将rdlist中的epitem拷贝到txlist中,并将rdlist清空。
ep_send_events函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对用的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。
从上边的分析中,我们就可以看到,epoll为何如此高效。
---------------------
摘自:https://blog.csdn.net/u013679744/article/details/79188768
参考:
select、poll、epoll的原理:https://blog.csdn.net/davidsguo008/article/details/73556811
https://blog.csdn.net/u013679744/article/details/79188768
https://blog.csdn.net/lsgqjh/article/details/65629609
深入:
1、红黑树特性
参考:
教你初步了解红黑树:https://blog.csdn.net/v_JULY_v/article/details/6105630
红黑树与二叉树、平衡二叉树对比:https://www.cnblogs.com/wuchanming/p/4444961.html
2、双端链表的快速排序
----待补充
2、Netty原理
1、I/O复用模型
2、Reactor 线程模型
Reactor 是反应堆的意思,Reactor 模型是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式。
服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程,Reactor 模式也叫 Dispatcher 模式,即 I/O 多了复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器的必备技术之一。
Reactor 模型中有 2 个关键组成:
Reactor,Reactor 在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人。
Handlers,处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。
3、Netty线程模型
主从 Rreactor 多线程模型
4、Netty功能特性图
Netty 功能特性如下:
传输服务,支持 BIO 和 NIO。
容器集成,支持 OSGI、JBossMC、Spring、Guice 容器。
协议支持,HTTP、Protobuf、二进制、文本、WebSocket 等一系列常见协议都支持。还支持通过实行编码解码逻辑来实现自定义协议。
Core 核心,可扩展事件模型、通用通信 API、支持零拷贝的 ByteBuf 缓冲对象。
5、Netty Reactor工作架构图
服务端 Netty Reactor 工作架构图
参考:
Netty原理解析:http://www.sohu.com/a/272879207_463994
Netty与Mina对比:https://blog.csdn.net/qq_33314107/article/details/81292589
21、HTTP请求流程和RPC调用流程及原理
RPC通信原理:
https://www.jianshu.com/p/78f72ccf0377
周边:
Netty IO:见上。
序列化、反序列化
序列化、反序列化原理:https://blog.csdn.net/xlgen157387/article/details/79840134
什么能序列化,什么不能?https://blog.csdn.net/xlgen157387/article/details/79840134
ProtoBuf的优缺点:https://www.jianshu.com/p/293c1e7c0a99
ZooKeeper使用及原理:
参考:
ZooKeeper使用及原理:https://blog.csdn.net/u010963948/article/details/83381757
参考:
HTTP请求原理:https://blog.csdn.net/yuntian1995/article/details/77513784
https://blog.csdn.net/lj402159806/article/details/60882615
输入URL发生了什么?https://www.cnblogs.com/daijinxue/p/6640153.html
HTTP:https://www.cnblogs.com/linjiqin/p/3560152.html
一次HTTP请求的过程:https://blog.csdn.net/hanziang1996/article/details/78982009
22、幂等性的理解,怎么实现幂等性
幂等性:就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。如多次点击不能重复扣款等。
解决方案:
1、单次请求,判断id
2、状态查询,判断订单状态等
参考:
分布式幂等性:https://www.cnblogs.com/leechenxiang/p/6626629.html
幂等性保证:https://www.cnblogs.com/javalyy/p/8882144.html
23、过滤器和拦截器的区别
区别:
(1)、Filter需要在web.xml中配置,依赖于Servlet;
(2)、Interceptor需要在SpringMVC中配置,依赖于框架;
(3)、Filter的执行顺序在Interceptor之前,具体的流程见下图;
(4)、两者的本质区别:拦截器(Interceptor)是基于Java的反射机制,而过滤器(Filter)是基于函数回调。从灵活性上说拦截器功能更强大些,Filter能做的事情,都能做,而且可以在请求前,请求后执行,比较灵活。Filter主要是针对URL地址做一个编码的事情、过滤掉没用的参数、安全校验(比较泛的,比如登录不登录之类),太细的话,还是建议用interceptor。不过还是根据不同情况选择合适的。
摘自:https://blog.csdn.net/zxd1435513775/article/details/80556034
参考:
过滤器、拦截器:https://www.cnblogs.com/junzi2099/p/8022058.html
https://www.cnblogs.com/panxuejun/p/7715917.html
24、MySQL锁怎么实现?Java锁实现及优化
MySQL行锁:
在并发事务处理带来的问题中,“更新丢失”通常应该是完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本可以分为以下两种。
一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。
另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。
在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。
在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:
1、快照读:简单的select操作,属于快照读,不加锁。(已经加了排他锁也可以这样查询记录)
select * from table where ?;
2、当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
下面语句都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
InnoDB锁:
InnoDB行锁是通过给索引上的索引项加锁来实现的。所以,
1、只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
2、使用不同的索引列,不同的事务也可以锁定不同的行
3、使用同一索引的不同事务,存在锁冲突。
4、条件中使用了索引,但是MySQL认为全表扫描更高效,会放弃使用索引,此时也是表锁,具体需要看执行计划。
InnoDB通过索引添加共享锁、排他锁
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。
意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
间隙锁(Next-Key锁):
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;
对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
举例来说,假如emp表中只有101条记录,其empid的值分别是 1,2,…,100,101,下面的SQL:
Select * from emp where empid > 100 for update;
1
是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使 用间隙锁,如果其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需 要。有关其恢复和复制对锁机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况,在后续的章节中会做进一步介绍。
很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。
还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件请求给一个不存在的记录加锁,InnoDB也会使用间隙锁!
参考:
MySQL锁详解:https://blog.csdn.net/soonfly/article/details/70238902
MySQL的行锁与表锁:https://blog.csdn.net/hxpjava1/article/details/79407961
锁算法:
参考:https://segmentfault.com/a/1190000014133576
锁优化:
Innodb行锁优化建议
尽可能让所有的数据检索都通过索引来完成,从而避免Innodb因为无法通过索引键加锁而升级为表级锁定;
合理设计索引,让Innodb在索引键上面加锁尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
尽可能减少基于范围的数据检索过滤条件,避免间隙锁带来的负面影响而锁定了不该锁定的记录;
尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
尽量使用较低的隔离级别; 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
选择合理的事务大小,小事务发生锁冲突的几率也更小;
给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。
参考:
MySQL锁原理深入:https://blog.csdn.net/zcl_love_wx/article/details/81983267
MySQL锁优化:https://blog.csdn.net/andong154564667/article/details/81019170
MVCC与Undo Redo日志:
MySQL InnoDB引擎,写数据前,先把当前数据写到Undo日志,便于多版本控制或者回滚保证事务的原子性,之后把更新后的数据写入Redo日志,产生脏页,后面由主线程写入磁盘。写的时候为了避免,redo日志写一半出现故障,进行了二次写的设计,先写入二次写缓存,然后在写入磁盘。如果写磁盘出现故障,也可以从二次写缓存中恢复过来。
MVCC实现原理:
MySql每行记录有三个隐藏字段,通过指向回滚版本的字段或删除版本标识,实现多版本的读取。
参考:https://blog.csdn.net/Jeaforea/article/details/82498120
MySQL索引优化及原理:
1、区分:聚簇索引、非聚簇索引
索引分为聚簇索引和非聚簇索引两种,聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引就不一样了;聚簇索引能提高多行检索的速度,而非聚簇索引对于单行的检索很快
mysql中,不同的存储引擎对索引的实现方式不同,大致说下MyISAM和InnoDB两种存储引擎。
MyISAM的B+Tree的叶子节点上的data,并不是数据本身,而是数据存放的地址。主索引和辅助索引没啥区别,只是主索引中的key一定得是唯一的。这里的索引都是非聚簇索引。
MyISAM还采用压缩机制存储索引,比如,第一个索引为“her”,第二个索引为“here”,那么第二个索引会被存储为“3,e”,这样的缺点是同一个节点中的索引只能采用顺序查找。
InnoDB的数据文件本身就是索引文件,B+Tree的叶子节点上的data就是数据本身,key为主键,这是聚簇索引。非聚簇索引,叶子节点上的data是主键(所以聚簇索引的key,不能过长)。为什么存放的主键,而不是记录所在地址呢,理由相当简单,因为记录所在地址并不能保证一定不会变,但主键可以保证。
至于为什么主键通常建议使用自增id呢?
答:聚簇索引的数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的。如果主键不是自增id,那么可以想象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但,如果是自增的,那就简单了,它只需要一页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。
使用推荐:
摘自:https://www.cnblogs.com/wyy123/p/6269875.html
2、索引类型:
1、主键索引:即主索引,根据主键pk_clolum(length)建立索引,不允许重复,不允许空值;
ALTER TABLE 'table_name' ADD PRIMARY KEY('col');
2、唯一索引:用来建立索引的列的值必须是唯一的,允许空值
ALTER TABLE 'table_name' ADD UNIQUE('col');
3、普通索引:用表中的普通列构建的索引,没有任何限制
ALTER TABLE 'table_name' ADD INDEX index_name('col');
4、全文索引:用大文本对象的列构建的索引(下一部分会讲解)
ALTER TABLE 'table_name' ADD FULLTEXT('col');
5、组合索引:用多个列组合构建的索引,这多个列中的值不允许有空值
ALTER TABLE 'table_name' ADD INDEX index_name('col1','col2','col3');
*遵循“最左前缀”原则,把最常用作为检索或排序的列放在最左,依次递减,组合索引相当于建立了col1,col1col2,col1col2col3三个索引,而col2或者col3是不能使用索引的。
3、索引原理
MySQL InnoDB的索引:B+树。
为什么使用B+树?
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数。
而B+树I/O存取次数最少。
BTree索引
BTree是平衡搜索多叉树,设树的度为d(d>1),高度为h,那么BTree要满足以一下条件:
每个叶子结点的高度一样,等于h;
每个非叶子结点由n-1个key和n个指针point组成,其中d<=n<=2d,key和point相互间隔,结点两端一定是key;
叶子结点指针都为null;
非叶子结点的key都是[key,data]二元组,其中key表示作为索引的键,data为键值所在行的数据;
BTree的结构如下:
在BTree的机构下,就可以使用二分查找的查找方式,查找复杂度为h*log(n),一般来说树的高度是很小的,一般为3左右,因此BTree是一个非常高效的查找结构。
B+Tree索引
B+Tree是BTree的一个变种,设d为树的度数,h为树的高度,B+Tree和BTree的不同主要在于:
B+Tree中的非叶子结点不存储数据,只存储键值;
B+Tree的叶子结点没有指针,所有键值都会出现在叶子结点上,且key存储的键值对应的数据的物理地址;
B+Tree的结构如下:
一般来说B+Tree比BTree更适合实现外存的索引结构,因为存储引擎的设计专家巧妙的利用了外存(磁盘)的存储结构,即磁盘的一个扇区是整数倍的page(页),页是存储中的一个单位,通常默认为4K,因此索引结构的节点被设计为一个页的大小,然后利用外存的“预读取”原则,每次读取的时候,把整个节点的数据读取到内存中,然后在内存中查找,已知内存的读取速度是外存读取I/O速度的几百倍,那么提升查找速度的关键就在于尽可能少的磁盘I/O,那么可以知道,每个节点中的key个数越多,那么树的高度越小,需要I/O的次数越少,因此一般来说B+Tree比BTree更快,因为B+Tree的非叶节点中不存储data,就可以存储更多的key。
带顺序索引的B+TREE
很多存储引擎在B+Tree的基础上进行了优化,添加了指向相邻叶节点的指针,形成了带有顺序访问指针的B+Tree,这样做是为了提高区间查找的效率,只要找到第一个值那么就可以顺序的查找后面的值。
MySQL聚簇索引与非聚簇对比:
MyISAM——非聚簇索引
MyISAM存储引擎采用的是非聚簇索引,非聚簇索引的主索引和辅助索引几乎是一样的,只是主索引不允许重复,不允许空值,他们的叶子结点的key都存储指向键值对应的数据的物理地址。
非聚簇索引的数据表和索引表是分开存储的。
非聚簇索引中的数据是根据数据的插入顺序保存。因此非聚簇索引更适合单个数据的查询。插入顺序不受键值影响。
只有在MyISAM中才能使用FULLTEXT索引。
*最开始我一直不懂既然非聚簇索引的主索引和辅助索引指向相同的内容,为什么还要辅助索引这个东西呢,后来才明白索引不就是用来查询的吗,用在那些地方呢,不就是WHERE和ORDER BY 语句后面吗,那么如果查询的条件不是主键怎么办呢,这个时候就需要辅助索引了。
InnoDB——聚簇索引
聚簇索引的主索引的叶子结点存储的是键值对应的数据本身,辅助索引的叶子结点存储的是键值对应的数据的主键键值。因此主键的值长度越小越好,类型越简单越好。
聚簇索引的数据和主键索引存储在一起。
聚簇索引的数据是根据主键的顺序保存。因此适合按主键索引的区间查找,可以有更少的磁盘I/O,加快查询速度。但是也是因为这个原因,聚簇索引的插入顺序最好按照主键单调的顺序插入,否则会频繁的引起页分裂,严重影响性能。
在InnoDB中,如果只需要查找索引的列,就尽量不要加入其它的列,这样会提高查询效率。
*使用主索引的时候,更适合使用聚簇索引,因为聚簇索引只需要查找一次,而非聚簇索引在查到数据的地址后,还要进行一次I/O查找数据。
*因为聚簇辅助索引存储的是主键的键值,因此可以在数据行移动或者页分裂的时候降低委会成本,因为这时不用维护辅助索引。但是辅助索引会占用更多的空间。
*聚簇索引在插入新数据的时候比非聚簇索引慢很多,因为插入新数据时需要减压主键是否重复,这需要遍历主索引的所有叶节点,而非聚簇索引的叶节点保存的是数据地址,占用空间少,因此分布集中,查询的时候I/O更少,但聚簇索引的主索引中存储的是数据本身,数据占用空间大,分布范围更大,可能占用好多的扇区,因此需要更多次I/O才能遍历完毕。
下图可以形象的说明聚簇索引和非聚簇索引的区别
摘自:https://blog.csdn.net/tongdanping/article/details/79878302
4、覆盖索引(Covering Indexes):
如果索引包含满足查询的所有数据,就称为覆盖索引。覆盖索引是一种非常强大的工具,能大大提高查询性能。只需要读取索引而不用读取数据有以下一些优点:
(1)索引项通常比记录要小,所以MySQL访问更少的数据;
(2)索引都按值的大小顺序存储,相对于随机访问记录,需要更少的I/O;
(3)大多数据引擎能更好的缓存索引。比如MyISAM只缓存索引。
(4)覆盖索引对于InnoDB表尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引中包含查询所需的数据,就不再需要在聚集索引中查找了。
覆盖索引不能是任何索引,只有B-TREE索引存储相应的值。而且不同的存储引擎实现覆盖索引的方式都不同,并不是所有存储引擎都支持覆盖索引(Memory和Falcon就不支持)。
对于索引覆盖查询(index-covered query),使用EXPLAIN时,可以在Extra一列中看到“Using index”。例如,在sakila的inventory表中,有一个组合索引(store_id,film_id),对于只需要访问这两列的查询,MySQL就可以使用索引,如下:
mysql> EXPLAIN SELECT store_id, film_id FROM sakila.inventory\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: inventory
type: index
possible_keys: NULL
key: idx_store_id_film_id
key_len: 3
ref: NULL
rows: 5007
Extra: Using index
1 row in set (0.17 sec)
在大多数引擎中,只有当查询语句所访问的列是索引的一部分时,索引才会覆盖。但是,InnoDB不限于此,InnoDB的二级索引(辅助索引、非聚集索引)在叶子节点中存储了primary key的值。因此,sakila.actor表使用InnoDB,而且对于是last_name上有索引,所以,索引能覆盖那些访问actor_id的查询,如:
mysql> EXPLAIN SELECT actor_id, last_name
-> FROM sakila.actor WHERE last_name = 'HOPPER'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: actor
type: ref
possible_keys: idx_actor_last_name
key: idx_actor_last_name
key_len: 137
ref: const
rows: 2
Extra: Using where; Using index
摘自:https://blog.csdn.net/tongdanping/article/details/79878302
5、索引使用建议:
五、索引的使用策略
什么时候要使用索引?
主键自动建立唯一索引;
经常作为查询条件在WHERE或者ORDER BY 语句中出现的列要建立索引;
作为排序的列要建立索引;
查询中与其他表关联的字段,外键关系建立索引
高并发条件下倾向组合索引;
什么时候不要使用索引?
经常增删改的列不要建立索引;
有大量重复的列不建立索引;
表记录太少不要建立索引;
*在组合索引中不能有列的值为NULL,如果有,那么这一列对组合索引就是无效的;
*在一个SELECT语句中,索引只能使用一次,如果在WHERE中使用了,那么在ORDER BY中就不要用了;
*LIKE操作中,'%aaa%'不会使用索引,也就是索引会失效,但是‘aaa%’可以使用索引;
*在索引的列上使用表达式或者函数会使索引失效,例如:select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。
*在查询条件中使用正则表达式时,只有在搜索模板的第一个字符不是通配符的情况下才能使用索引。
*在查询条件中使用<>会导致索引失效。
*在查询条件中使用IS NULL会导致索引失效。
*在查询条件中使用OR连接多个条件会导致索引失效,这时应该改为两次查询,然后用UNION ALL连接起来。
*尽量不要包括多列排序,如果一定要,最好为这队列构建组合索引;
*只有当数据库里已经有了足够多的测试数据时,它的性能测试结果才有实际参考价值。如果在测试数据库里只有几百条数据记录,它们往往在执行完第一条查询命令之后就被全部加载到内存里,这将使后续的查询命令都执行得非常快--不管有没有使用索引。只有当数据库里的记录超过了1000条、数据总量也超过了MySQL服务器上的内存总量时,数据库的性能测试结果才有意义。
摘自:https://blog.csdn.net/tongdanping/article/details/79878302
1、MySQL只对一下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)
2、缺省情况下建立的索引是非聚簇索引,但有时它并不是最佳的。在非群集索引下,数据在物理上随机存放在数据页上。合理的索引设计要建立在对各种查询的分析和预测上。一般来说:
a.有大量重复值、且经常有范围查询( > ,< ,> =,< =)和order by、group by发生的列,可考
虑建立群集索引;
b.经常同时存取多列,且每列都含有重复值可考虑建立组合索引;
c.组合索引要尽量使关键查询形成索引覆盖,其前导列一定是使用最频繁的列。索引虽有助于提高性能但不是索引越多越好,恰好相反过多的索引会导致系统低效。用户在表中每加进一个索引,维护索引集合就要做相应的更新工作。
3、ORDER BY和GROPU BY使用ORDER BY和GROUP BY短语,任何一种索引都有助于SELECT的性能提高。
4、索引不会包含有NULL值的列
5、多表操作在被实际执行前,查询优化器会根据连接条件,列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表;内外表的选择可由公式:外层表中的匹配行数*内层表中每一次查找的次数确定,乘积最小为最佳方案。
6、任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,查询时要尽可能将操作移至等号右边。
7、IN、OR子句常会使用工作表,使索引失效。如果不产生大量重复值,可以考虑把子句拆开。拆开的子句中应该包含索引。
参考:
深入索引原理:https://blog.csdn.net/tongdanping/article/details/79878302
mysql索引的使用和优化:https://www.cnblogs.com/doudouxiaoye/p/5831449.html
聚集、非聚集索引:https://www.cnblogs.com/wyy123/p/6269875.html
索引及优化:https://blog.csdn.net/forezp/article/details/88325144
25、序列化、反序列化
参考:https://blog.csdn.net/xlgen157387/article/details/79840134
26、微服务架构的理念
参考:
什么是微服务:https://blog.csdn.net/fly_zhyu/article/details/76408158
微服务:https://www.cnblogs.com/imyalost/p/6792724.html
微服务架构模式:https://www.cnblogs.com/duanxz/p/3514895.html
微服务架构几种模式:https://www.cnblogs.com/mowei/p/7126275.html
27、数据库的ACID怎么实现的?
参考:
ACID怎么实现的?https://blog.csdn.net/when_less_is_more/article/details/70183327
28、MySQL的多版本控制如何实现?
MVCC:
mvcc多版本快照由innodb的rollback segment构照的,一个sql进行查找数据当查找到某一个数据需要到回滚段中查找数据时,就会根据当前页上行数据的一个指针到回滚段中查找对应数据,在innodb的表主键中都会存在三个隐藏的字段:
DB_TRX_ID:该字段存储最后一个修改该行数据的事务ID,占用6byte的空间,MySQL的delete操作是标记删除,所以对应行数据的该字段就为一个删除标记。
DB_ROLL_PTR:该字段就记录执行roll segment的指针信息,当事务需要rollback时就通过该字段寻找记录重新构照行数据,该字段占用7byte空间。
DB_ROW_ID:记录每个行ID,该ID值为单调递增型整数,在innodb表指定了主键之后DB_ROW_ID存在于主键索引上,如果无主键该值就不会存在,占用6byte空间。
在一个sql进行查询时,读取到一行数据的DB_TRX_ID值和自己事物ID的对比,假如隔离级别为MySQL的默认级别,就只读取该ID值小于本身事物ID的数据,其余数据就需要通过DB_ROLL_PTR的信息到回滚段中读取。MVCC是否起到相应的作用需取决于数据库隔离级别的配置。
MySQL MVCC:https://www.cnblogs.com/jinshiyill/p/5355703.html
https://blog.csdn.net/aoerqileng/article/details/51337487
Redo和Undo日志:https://blog.csdn.net/chast_cn/article/details/50910861
29、Java的原子性、有序性、可见性,内存模型
1、共享内存:
主内存 —— 即main memory。在java中,实例域、静态域和数组元素是线程之间共享的数据,它们存储在主内存中。
本地内存 —— 即local memory。 局部变量,方法定义参数 和 异常处理器参数是不会在线程之间共享的,它们存储在线程的本地内存中。
2、指令重排序
编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。
对于处理器重排序,JMM 的处理器重排序规则会要求 java 编译器在生成指令序列时,插入特定类型的内存屏障(memory barriers,intel 称之为 memory fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序(不是所有的处理器重排序都要禁止)
3、happens-before
与程序员密切相关的 happens-before 规则如下:
程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
volatile 变量规则:对一个 volatile 域的写,happens- before 于任意后续对这个 volatile 域的读。
传递性:如果 A happens- before B,且 B happens- before C,那么 A happens- before C。
4、as-if-serial
定义:不管怎么重排序,程序的执行结果不能被改变。
5、原子性
是指一个操作是按原子的方式执行的。要么该操作不被执行;要么以原子方式执行,即执行过程中不会被其它线程中断。
(1)Synchronized:保证可见性和原子性
Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。
(2)Volatile:保证可见性,但不保证操作的原子性
Volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性
参考:
Java内存模型:https://www.cnblogs.com/skywang12345/p/3447546.html
Java内存模型(happen-before):https://www.jianshu.com/p/d52fea0d6ba5
Java内存模型:https://infoq.cn/article/java-memory-model-1
Java的内存可见性与原子性:https://blog.csdn.net/guyuealian/article/details/52525724
37、堆,什么是最小堆,最大堆,在堆中怎么插入一个数据
基本概念:
1、完全二叉树:若二叉树的深度为h,则除第h层外,其他层的结点全部达到最大值,且第h层的所有结点都集中在左子树。
2、满二叉树:满二叉树是一种特殊的的完全二叉树,所有层的结点都是最大值。
定义:
1、堆是一颗完全二叉树;
2、堆中的某个结点的值总是大于等于(最大堆)或小于等于(最小堆)其孩子结点的值。
3、堆中每个结点的子树都是堆树。
参考:https://blog.csdn.net/summerlq/article/details/82747940
https://blog.csdn.net/qq_28171461/article/details/78855987
38、MQ的技术选型,为什么选这个,项目中怎么用,底层怎么实现的,怎么保证稳定的消费者生产者队列
消息中间件对比:https://blog.csdn.net/u013256816/article/details/79838428
Kafka与RabbitMQ对比:https://www.sojson.com/blog/48.html
RocketMQ事务实现:https://www.jianshu.com/p/453c6e7ff81c
39、双向链表的快排
参考:
https://www.jianshu.com/p/754207177beb
https://blog.csdn.net/chary8088/article/details/7719721
40、MVC分别怎么实现的
MVC:model,view,controller
model:应用的业务逻辑(如:数据库的操作),通过JavaBean实现
(hibernate、mybatis、ibatis)
view:视图层,用于与用户的交互,主要由jsp页面产生。
(jsp、FreeMarker、tails、taglib、EL、Velocity )
controller:处理过程控制,一般是一个servlet。
它可以分派用户的请求并选择恰当的视图以用于显示
同时它也可以解释用户的输入并将它们映射为模型层可执行的操作。
(severlet、struts、spring、action)
转自:https://blog.csdn.net/zangdaiyang1991/article/details/89893090