Neo's Blog

不抽象就无法深入思考
不还原就看不到本来面目!

0%

端口复用,通过互斥锁来避免惊群效应

为什么不采用多线程模型管理连接

  1. 无状态服务,无需共享进程内存
  2. 采用独立的进程,可以让互相之间不会影响。一个进程异常崩溃,其他进程的服务不会中断,提升了架构的可靠性。
  3. 进程之间不共享资源,不需要加锁,所以省掉了锁带来的开销。

为什么不采用多线程处理逻辑业务?

  1. 进程数已经等于核心数,再新建线程处理任务,只会抢占现有进程,增加切换代价。
  2. 作为接入层,基本上都是数据转发业务,网络 IO 任务的等待耗时部分,已经被处理为非阻塞/全异步/事件驱动模式,在没有更多 CPU 的情况下,再利用多线程处理,意义不大。
  3. 并且如果进程中有阻塞的处理逻辑,应该由各个业务进行解决,比如 openResty 中利用了 Lua 协程,对阻塞业务进行了优化。

select

不足:

  1. 通过数组来管理fd,数量限制
  2. 触发方式单一,仅支持水平触发(LT)

poll

优点:通过链条来管理fd,突破数量限制

不足:

  1. 当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分socket是“活跃”的,但是select/poll每次调用都会线性扫描全部的集合(时间复杂度是$O(n)$,导致效率呈线性下降。
  2. 内核与用户态的数据拷贝

epoll

特点:

  1. 没有最大并发连接的限制
  2. 通过红黑树管理所有的fd,时间复杂度为$O(lgn)$
  3. 更丰富的触发方式,同时支持水平触发(LT)与边缘触发(ET)

Reactor模型

image

单线程Reactor模型
优点:单 Reactor 单进程的方案因为全部工作都在同一个进程内完成,所以实现起来比较简单,不需要考虑进程间通信,也不用担心多进程竞争。
不足:
(1)第一个缺点,因为只有一个进程,无法充分利用 多核 CPU 的性能;【该缺点可以部署多个进程来解决】
(2)第二个缺点,Handler 对象在业务处理时,整个进程是无法处理其他连接的事件的,如果业务处理耗时比较长,那么就造成响应的延迟;
使用场景:CPU密集型
例子:Redis

image

单 Reactor 多线程 / 多进程
优点:能够充分利用多核CPU
不足:
(1)子线程完成业务处理后,要把结果传递给主线程的 Reactor 进行发送,这里涉及共享数据的竞争。
(2)因为一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方。
使用场景:
例子:?

image

多 Reactor 多线程 / 多进程
优点:克服单 Reactor 多线程 / 多进程的缺点2,主线程不会成为瓶颈;【相对上面的方案,实现更加简单一些】主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理;主线程和子线程的交互很简单,主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端。
不足:
使用场景:
例子:memcache、Netty

多 Reactor 多线程 / 多进程(变种)
思路:进一步的放大子线程/子进程的工作,把accept的工作也交给了子进程。
使用场景:
例子:Nginx

判断使用同步或异步

计算qps * latency(in seconds)【最大并发】,如果和cpu核数是同一数量级,就用同步,否则用异步。

eg. qps = 2000,latency = 10ms,计算结果 = 2000 * 0.01s = 20
和常见的32核在同一个数量级,用同步。

eg. qps = 100, latency = 5s, 计算结果 = 100 * 5s = 500。和核数不在同一个数量级,用异步。

eg. qps = 500, latency = 100ms,计算结果 = 500 * 0.1s = 50。基本在同一个数量级,可用同步。如果未来延时继续增长,考虑异步。

这个公式计算的是同时进行的平均请求数(你可以尝试证明一下),和线程数,cpu核数是可比的。当这个值远大于cpu核数时,说明大部分操作并不耗费cpu,而是让大量线程阻塞着,使用异步可以明显节省线程资源(栈占用的内存)。当这个值小于或和cpu核数差不多时,异步能节省的线程资源就很有限了,这时候简单易懂的同步代码更重要。

异步或bthread

有了bthread这个工具,用户甚至可以自己实现异步。以“半同步”为例,在brpc中用户有多种选择:

发起多个异步RPC后挨个Join,这个函数会阻塞直到RPC结束。(这儿是为了和bthread对比,实现中我们建议你使用ParallelChannel,而不是自己Join)
启动多个bthread各自执行同步RPC后挨个join bthreads。
哪种效率更高呢?显然是前者。后者不仅要付出创建bthread的代价,在RPC过程中bthread还被阻塞着,不能用于其他用途。

如果仅仅是为了并发RPC,别用bthread。

不过当你需要并行计算时,问题就不同了。使用bthread可以简单地构建树形的并行计算,充分利用多核资源。比如检索过程中有三个环节可以并行处理,你可以建立两个bthread运行两个环节,在原地运行剩下的环节,最后join那两个bthread。过程大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool search() {
...
bthread th1, th2;
if (bthread_start_background(&th1, NULL, part1, part1_args) != 0) {
LOG(ERROR) << "Fail to create bthread for part1";
return false;
}
if (bthread_start_background(&th2, NULL, part2, part2_args) != 0) {
LOG(ERROR) << "Fail to create bthread for part2";
return false;
}
part3(part3_args);
bthread_join(th1);
bthread_join(th2);
return true;
}

这么实现的point:

  • 你当然可以建立三个bthread分别执行三个部分,最后join它们,但相比这个方法要多耗费一个线程资源。
  • bthread从建立到执行是有延时的(调度延时),在不是很忙的机器上,这个延时的中位数在3微秒左右,90%在10微秒内,99.99%在30微秒内。这说明两点:计算时间超过1ms时收益比较明显。如果计算非常简单,几微秒就结束了,用bthread是没有意义的。尽量让原地运行的部分最慢,那样bthread中的部分即使被延迟了几微秒,最后可能还是会先结束,而消除掉延迟的影响。并且join一个已结束的bthread时会立刻返回,不会有上下文切换开销。

round-robin

总是选择列表中的下一台服务器,结尾的下一台是开头,无需其他设置。比如有3台机器a,b,c,那么brpc会依次向a, b, c, a, b, c, …发送请求。注意这个算法的前提是服务器的配置,网络条件,负载都是类似

Weighted Round Robin

根据服务器列表配置的权重值来选择服务器。服务器被选到的机会正比于其权重值,并且该算法能保证同一服务器被选到的结果较均衡的散开

不足:

  1. 无法快速摘除有问题的节点
  2. 无法均衡后端负载
  3. 无法降低总体延迟

nginx的WRR算法原理如下:

每个服务器都有两个权重变量:

a:weight,配置文件中指定的该服务器的权重,这个值是固定不变的;

b:current_weight,服务器目前的权重。一开始为0,之后会动态调整。

每次当请求到来,选取服务器时,会遍历数组中所有服务器。对于每个服务器,让它的current_weight增加它的weight;同时累加所有服务器的weight,并保存为total。

遍历完所有服务器之后,如果该服务器的current_weight是最大的,就选择这个服务器处理本次请求。最后把该服务器的current_weight减去total。

nginx_WRR

动态感知版的Weighted Round Robin

动态感知的WRR
peer.score = success_rate /(lantency * cpuUsage)

具体做法:

  1. 利用每次RPC请求返回的Response夹带CPU使用率
  2. 每隔一段时间整体调整一次节点的权重分数

不足:

自动刷新权重值,但是在刷新时无法做到完全的实时,再快也不可能超过一个 RTT,都会存在一些信息延迟差。当后台资源比较稀缺时,遇到网络抖动时,就可能会把该节点炸掉,但是在监控上面是感觉不到的,因为 CPU 已经被平均掉了。

best of two random choice

repsonse中附带cpu使用率,信息滞后造成了严重的羊群效应

计算权重分数,每次请求来时我们都会更新延迟,并且把之前获得的时间延迟进行权重的衰减,新获得的时间提高权重,这样就实现了滚动更新

带系数的滑动平均值
时间衰减系数的滑动平均值:伴随时间,老的均值,权重越来越小,新的response time,越来越高

randomized

随机法,是随机选择一台服务器来分配任务。它保证了请求的分散性达到了均衡的目的。同时它是没有状态的不需要维持上次的选择状态和均衡因子。但是随着任务量的增大,它的效果趋向轮询后也会具有轮询算法的部分缺点。

根据随机算法,将请求随机分配到后端服务器中,请求的均匀请求依赖于随机算法,该实现方式较为简单,常常可以配合处理一些极端的请求,例如热点请求情况。不适合对命中率有要求的场景。

consistent-hashing

Hash哈希是根据Source IP、 Destination IP、URL、或者其它,算hash值或者md5,再采用取模,相同的请求会请求到同一个后端服务器中。该算法无法解决热点请求,会把某个时间段的热点请求路由到某个单机上,造成雪崩效应,同时在扩充和节点宕机时发生命中率急剧降低的问题(hash算法导致),该策略适合维护长连接和提高命中率。Consistanct Hash是对Hash 算法的优化,可以有效的解决宕机和扩充造成的命中率急剧降低的问题。

一致性Hash算法也是使用取模的方法,只是,刚才描述的取模法是对服务器的数量进行取模,而一致性Hash算法是对2^32取模,什么意思呢?简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0~2^32-1(即哈希值是一个32位无符号整形)。

整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1, 0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。

下一步将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。

brpc locality-aware

优先选择延时低的下游,直到其延时高于其他机器,无需其他设置

在DP 2.0中我们使用了一种新的算法: Locality-aware load balancing,能根据下游节点的负载分配流量,还能快速规避失效的节点,在很大程度上,这种算法的延时也是全局最优的。基本原理非常简单:

以下游节点的吞吐除以延时作为分流权值。

比如只有两台下游节点,W代表权值,QPS代表吞吐,L代表延时,那么W1 = QPS1 / L1和W2 = QPS2 / L2分别是这两个节点的分流权值,分流时随机数落入的权值区间就是流量的目的地了。

一种分析方法如下:

稳定状态时的QPS显然和其分流权值W成正比,即W1 / W2 ≈ QPS1 / QPS2。
根据分流公式又有:W1 / W2 = QPS1 / QPS2 * (L2 / L1)。
故稳定状态时L1和L2应当是趋同的。当L1小于L2时,节点1会更获得相比其QPS1更大的W1,从而在未来获得更多的流量,直到其延时高于平均值或没有更多的流量。

具体见:

Locality-aware

任务:子任务
自然任务NatureTask,用户任务, TaskKey 用户邀请别人点赞达到10,自己去下单金额超过100(考虑逆向)

状态机(根据规则引擎的判定结果,决定状态机往哪边走)

变与不变,不变的部份,通过代码落地下来;变的部份,通过规则引擎来处理。

不变的部份:事件是可以枚举的,奖励是可以枚举的;
变的部份:任务参数会变,活动周期会变,累加规则会变,事件奖励的各种组合

写别样的代码…

PM运营沟通,让他们尽量少提没有通用性的需求;
熟悉业务的同时,给出自己的技术方案
给大家打鸡血,协同大家一起完成1.0版

救火案例2
B站微博系统,

单线程模型

代表:Redis

基本原理:

单一线程,依次socket->bind->listen,然后epoll_wait分别进行accept以及读写事件

优点&使用场景:

简单、没有并发锁问题;适用于短耗时的计算密集型服务

缺点:

不能支持耗时较长的事件,尤其是IO密集型

单线程(listen+accept+epoll_wait) + 1队列通知 + n线程(读写处理) 模型

代表:thrift-nonblocking-server

基本原理:

  1. 在这种模型中,有1+n个线程
  2. 有1个线程执行端口的listen并把listen_fd加入该线程的epoll_set,然后循环去做如下事情:

    2.1 epoll_wait监听新连接的到

    2.2 调用accept获得新到的fd

    2.3 把fd放入队列

    回到2.1,继续epoll_wait

  3. 另外有n个工作线程,从队列里面获取文件描述符,然后执行:1)读取数据,2)执行任务

优点:

  • 模型不算复杂
  • 并发能力强,能够充分利用多核
  • 天然支持负载均衡(每个工作工作线程完成任务之后就会去队列里主动获取文件描述符)

缺点:

队列可能是性能瓶颈,尤其是当业务逻辑耗时本身极其短的情况下

单线程(listen+accept+epoll_wait) + n队列通知 + n线程(读写处理) 模型

代表:memcache

基本原理:

  • 这种模型基本类似于上一种模型,区别在于把1个队列换成n个队列,每个工作线程绑定一个队列,每个工作线程从自己的队列消费数据,其他的保持一致

  • LISTEN线程往PIPE里写入一个哨兵,通知WORKER线程队列可读

优点:

并发能力更强。相比于单队列的模型,多队列的好处是减少了队列的锁竞争。对于短耗时任务能得到比较多的提升,很适合缓存类应用

缺点:

有可能导致负载不均。因为监听线程是不会去根据不同线程的处理速度决定把任务分配给哪个线程的,如果每个任务的耗时不均衡,那么就可能导致有些线程累死,有些线程饿死。

单线程listen, 在处理高速率海量连接时,单线程可能会成为瓶颈

单进程(listen) + n进程(accept + epoll + 读写处理) 模型

代表:nginx

基本原理:

  1. master进程监听新连接的到来,并让其中一个worker进程accept。这里需要处理惊群效应问题(加锁、SO_REUSEPORT)
  2. worker进程accept到fd之后,把fd注册到到本进程的epoll句柄里面,由本进程处理这个fd的后续读写事件
  3. worker进程根据自身负载情况,选择性地不去accept新fd,从而实现负载均衡

单线程(listen) + n线程(accept + epoll + 读写处理 + 协程) 模型

  1. 同时只有一个worker来accept接受新的连接请求。一个连接上的所有请求都是由同一个worker来处理。

  2. 通过iovec来接收输入, iovec可以直接交给pb来解析。

  3. 如果是一个新的请求包,则开启一个co由它来进行处理。

  4. 如果是一个回复包,则根据回复包的seqno, 查询map, 找到对应的co, co_resume, 执行原来的业务逻辑。

  5. 通过时间轮来管理所有的超时时间。

  6. epoll_wait的timeout 是 min(1ms, 下次超时时间)

  7. 通过pipe来进行线程同步。

高速缓存服务器(Cache Server)是软硬件高度集成的专业功能服务器,主要做高速缓存加速服务,一般部署在网络边缘。

根据加速对象不同,分为客户端加速和服务器加速

客户端加速Cache部署在网络出口处,把常访问的内容缓存在本地,提高响应速度和节约带宽;

服务器加速Cache部署在服务器前端,作为Web服务器的前置机,提高Web服务器的性能,加速访问速度。


缓存有哪些类型?

  • 服务器LocalCache (有状态)

将缓存内容放到服务器的本地内存或者磁盘;

无法及时更新,一般都是设置一个合理的过期时间,让其自动过期;适用于实时性要求不高的场景;

优缺点:本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。

特例:数据库缓存

  • 分布式缓存(无状态)

redis\memcache\tair

  • 客户端缓存(有状态)

具体有哪些:
浏览器cookie;浏览器本地缓存;flash本地存储;html5的本地存储;native app 本地缓存

缓存系统对比与选型


在Web应用领域,Web缓存大致可以分为以下几种类型:

浏览器端缓存

浏览器缓存根据一套与服务器约定的规则进行工作,在同一个会话过程中会检查一次并确定缓存的副本足够新。如果你浏览过程中,比如前进或后退,访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。

客户端浏览器缓存主要是通过在http头部增加
Last-Modified,If-Modified-Since,Expires,Cache-Control等标识,和服务器进行协商,是否是采用客户的本机缓存来实现。

其中这里面也会分为三种方式

1 通过Last-Modified,If-Modified-Since方式和服务器通信,客户发出http请求中包含If-Modified-Since,如果服务器端代码没有修改,服务器端返回302响应代码的请求响应头(内容不返回)客户端则直接用本机缓存的内容缓存显示结果。相
当于节省了服务器执行代码时间以及数据传输时间。

2 通过Expires,Cache-Control控制,客户端发现如果上次请求的页面还未过期,通过Expires或者Cache-Control进行辨别,则直接显示本机缓存的内容,不与服务器进行通信。

服务器端缓存

CDN缓存

CDN(Content delivery networks)缓存,也叫网关缓存、反向代理缓存。CDN缓存一般是由网站管理员自己部署,为了让他们的网站更容易扩展并获得更好的性能。浏览器先向CDN网关发起Web请求,网关服务器后面对应着一台或多台负载均衡源服务器,会根据它们的负载请求,动态将请求转发到合适的源服务器上。虽然这种架构负载均衡源服务器之间的缓存没法共享,但却拥有更好的处扩展性。从浏览器角度来看,整个CDN就是一个源服务器,从这个层面来说,本文讨论浏览器和服务器之间的缓存机制,在这种架构下同样适用。

代理服务器缓存

代理服务器是浏览器和源服务器之间的中间服务器,浏览器先向这个中间服务器发起Web请求,经过处理后(比如权限验证,缓存匹配等),再将请求转发到源服务器。代理服务器缓存的运作原理跟浏览器的运作原理差不多,只是规模更大。可以把它理解为一个共享缓存,不只为一个用户服务,一般为大量用户提供服务,因此在减少相应时间和带宽使用方面很有效,同一个副本会被重用多次。常见代理服务器缓存解决方案有Squid等

Web应用层缓存

应用层缓存指的是从代码层面上,通过代码逻辑和缓存策略,实现对数据,页面,图片等资源的缓存,可以根据实际情况选择将数据存在文件系统或者内存中,减少数据库查询或者读写瓶颈,提高响应效率。

数据库数据缓存

Web应用,特别是SNS类型的应用,往往关系比较复杂,数据库表繁多,如果频繁进行数据库查询,很容易导致数据库不堪重荷。为了提供查询的性能,会将查询后的数据放到内存中进行缓存,下次查询时,直接从内存缓存直接返回,提供响应效率。比如常用的缓存方案有memcached等。

总结一下:
一般的高并发的应用程序,都在web层采用了以上几种缓存,一般静态资源(图片,js,css)都会采用nginx反向代理+客户端缓存来实现
对于门户网站,尤其是首页的新闻,一般都会缓存起来,可以通过反向代理也可以通过应用程序缓存实现方式
对于下载或者视频网站,由于数据传输比较大,直接采用浏览器本地缓存实现。


业务需求可能涉及的缓存组件要求:

容量

需要存储什么类型的内容? 存储量多大?

并发(qps)

缓存的读写比例以及qps如何?

响应时间

更高的qps可以通过扩容来解决,但是一次响应的时间是有限制的,例如跨机房访问延迟0.5ms,就决定了响应时间不可能低于0.5ms;

反过来,如果业务上要求的resp time必须小于0.5ms, 那么该缓存就一定满足不了我们的要求。

使用成本

分为两部分:

  1. 首先服务端,主要包括:运维成本、机器成本
  2. 第二,客户端,主要包括程序员研发成本:单一的库依赖、简洁的配置和人性化的API丰富的文档和技术支持
扩展性要求

在某方面出现瓶颈(qps或者容量)时,能否通过增加机器来快速在线扩容;这个主要涉及到系统的负载均衡能力;

容灾能力

缓存数据丢失;不同的系统有不同的容灾能力;一定要结合业务需求

memcached作为高速运行的分布式缓存服务器,具有以下的特点:

(1)协议简单,文本协议

(2)基于libevent的事件处理,对服务器的连接数增加,也能发挥O(1)的性能

操作:

(1)set, get, del

(2)多key查询

(3)原子自增,自减

memcache内存分配

Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。但是存在内存浪费问题!!(外碎片没有了,但是当分配大小超过实际要用大小时,产生内部碎片)

memcache删除机制

Lazy Expiration

基于LRU(Least Recently Used)算法自动删除不使用的缓存

memcached内部不会监视记录是否过期,而是在get时查看记录的时间戳,检查记录是否过期。这种技术被称为lazy(惰性)expiration。因此,memcached不会在过期监视上耗费CPU时间。

当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期 key 进行清理,还会按 LRU 策略对数据进行剔除。

1.4以前:

维护一个双向链表,当被访问时,移动到head; 当需要淘汰时,从尾巴开始扫描,找到已经过期的item淘汰掉。

1.5以后:

维护子LRU(HOT\WARM\COLD),有点类似多级队列。

哈希算法

memcached不互相通信的分布式,由客户端来实现

根据余数计算分散

数据的分散性也相当优秀,但也有其缺点。那就是当添加或移除服务器时,缓存重组的代价相当巨大。
添加服务器后,余数就会产生巨变,这样就无法获取与保存时相同的服务器,从而影响缓存的命中率。

一致性Hash

但Consistent Hashing中,只有在continuum(统一连续体)上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响。Consistent Hashing最大限度地抑制了键的重新分布。

而且,有的Consistent Hashing的实现方法还采用了虚拟节点的思想。使用一般的hash函数的话,服务器的映射地点的分布非常不均匀。

因此,使用虚拟节点的思想,为每个物理节点(服务器)在continuum上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。

hash表扩容

基于双缓冲思想的扩容方案

启动一个后台线程,监控hash表大小,如果快满了,则拷贝到新的hash表【扩大容量】

事务的特性
  • A 原子性(Atomicity),要么执行要么不执行
  • C 一致性(Consistency),事务将数据库从一种状态转变为下一种一致的状态。在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。(事务的acid不是完全正交的,尤其是一致性,可能跟原子性、隔离性都有一定关系。一致性是一个更宏观的要求。)
  • I 隔离型(Isolation),数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read uncommitted)、提交读(read committed)、可重复读(repeatable read)和串行化(Serializable
  • D 持久性(Durability),一旦事务提交,对数据的改变就是永久的,即便系统故障也不会丢失

事务同时运行可能出现的问题

  • 脏读,事务B读到事务A还没有提交的数据
  • 不可重复读,一行被SELECT两次,返回的结果不一样
  • 幻读,两次读取返回的集合不一样
事务的四种隔离级别
  • 读未提交,在该隔离级别,会出现脏读、不可重复读、幻读等问题。
  • 读已提交,该隔离级别解决了脏读的问题,依旧会出现不可重复读、幻读问题。
  • 可重复读,该隔离解决进一步解决了不可重复读的问题,会出现幻读问题。(但是对于InnoDB存储引擎,通过间隙锁解决了该问题,不会出现幻读现象)
  • 串行,该隔离级别把所有操作都串行化了,没有并发访问,解决了以上所有问题。

数据库各隔离级别会出现的问题

Mysql_InnoDB引擎各隔离级别会出现的问题

虽说希望你了解,但是友情提示一波:线上高并发应用,尽量不要用事务

数据库索引一些经常考察的知识点

mysql数据库索引

  1. Mysql 数据库有哪些索引以及他们各自的特点?InnoDB为什么选择用B+树作为索引,而不用B树?
索引 特点 使用场景
hash索引 散列表实现,等值查询效率高,不能排序,不能进行范围查询 不需要范围查询,仅需等值查询时,可以考虑使用
BTree索引 B+树实现,支持范围查询 默认
RTree索引 仅支持地理位置类型,RTree空间树实现,相比Btree有更好的范围查询性能 有按照地理位置检索需要的场景
FullText索引 分词加倒排索引实现 有类似like的全文检索类型的查询

相比B树,B+树索引支持范围查找。

索引匹配原则:左前缀匹配原则

聚簇索引、非聚簇索引

索引 特点 使用场景
聚簇索引(clustered index) 数据按照索引顺序存储,叶子节点存储真实的数据 InnoDB索引
非聚簇索引(secondary index,non-clustered index) 叶子节点存储指向真正数据行的指针 MyISAM

聚簇索引与非聚簇索引

数据库回表

数据库回表是怎么回事?如何避免?

在查询辅助索引时,如果要查询的字段已经全部在索引中了,那么就不需要额外再查询主索引了;反之,如果要查询的字段当前索引无法覆盖,那么Mysql需要额外查询主索引去获取要查询的字段,访问索引的次数多了一次,我们称刚才的过程为回表。我们通过增加全覆盖索引可以避免回表。

InnoDB索引与MyISAM索引的区别
索引 特点 使用场景
InnoDB索引 InnoDB的主索引的叶子节点就是数据本身,而辅助索引的叶子节点是主键ID InnoDB索引
MyISAM索引 InnoDB的主索引与辅助索引没有区别,叶子节点存储都是指向真实数据行的指针 MyISAM
  1. 索引为什么要用B+树来实现?
    首先,相比红黑树、AVL等二叉平衡树,B+树更加矮胖,这样子索引查找便能够更好的访问磁盘IO,从而有更好的查询性能;另外相比B树,B+树在叶子节点之间维护了一根链表,借助该链表,范围查找性能更加稳定。

    MysqlB+树索引展示

  2. 两种存储引擎区别与各自使用场景

存储引擎 特点 使用场景
MyISAM 不支持外键,表锁,插入数据时锁定整个表,查表的总行数不需要扫表,索引与数据分开 不需要支持事务,绝大多数请求为读操作,系统崩溃后数据丢失可接受
InnoDB 支持外键,行级锁,事务,查表的总行数时需要扫表,必须有唯一索引,索引与数据在一个文件中 需要支持事务,读写相当,不可接受数据丢失
  1. 为什么说数据表超过2000W就会变慢?有理论依旧吗?

磁盘扇区、文件系统、InnoDB存储引擎都有各自的最小存储单元。

最小存储单元

非叶子节点由索引值和指针构成:主键假设8字节;指针8字节;所以一个页最多有多少个指针呢? 16k / 16 = 1000左右。

叶子节点直接存数据,假设数据大小为1k,那么一个叶子节点存了16条记录。

所以,B+树树高为1的话,存的记录最多为16 000; 高为2的话,1000 1000 * 16 = 1600w左右。

在内存有限的情况下,多加一层,就意味着要多一次磁盘IO,性能便会急剧下降。