操作系统、计网、数据库面经

操作系统

概念

操作系统本质上是一个运行在计算机上的软件程序 ,管理着计算机硬件和软件资源,为计算机硬件和软件提供了一种中间层,使应用软件和硬件进行分离,屏蔽了硬件层的复杂性,让我们把关注点更多放在软件应用上。操作系统的主要功能有:

  • (1)进程管理:进程管理的主要作用就是任务调度,以及进程的创建销毁、阻塞唤醒、进程同步、进程通信、死锁处理等功能。
  • (2)内存管理:内存分配与回收、地址映射、虚拟内存以及页面的置换
  • (3)文件管理:有效地管理文件的存储空间,合理地组织和管理文件系统,为文件访问和文件保护提供更有效的方法及手段。
  • (4)设备管理:根据确定的设备分配原则对设备进行分配,使设备与主机能够并行工作,为用户提供良好的设备使用界面。

用户态和内核态

用户态和系统态是操作系统的两种运行状态:

  • 内核态:内核态运行的程序可以访问计算机的任何数据和资源,不受限制,包括外围设备,比如网卡、硬盘等。处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况。
  • 用户态:用户态运行的程序只能受限地访问内存,只能直接读取用户程序的数据,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。

将操作系统的运行状态分为用户态和内核态,主要是为了对访问能力进行限制,防止随意进行一些比较危险的操作导致系统的崩溃,比如设置时钟、内存清理,这些都需要在内核态下完成

进程和线程

进程与线程的区别:

(1)一个程序至少有一个进程,一个进程至少有一个线程,线程是依赖于进程存在的,线程是一个进程中代码的不同的执行路线;

(2)进程是对运行时程序的封装,是操作系统进行资源调度和分配的最小单位,实现了操作系统的并发;线程是程序执行的最小单位,是CPU调度和分派的基本的单位,实现进程内部的并发。

(3)资源与内存空间:进程是资源分配的基本单位,线程不拥有资源;进程之间拥有相互独立的内存单位,但是同一个进程下的各个线程之间共享程序的内存空间,(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;

(4)系统开销:创建或销毁进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等;而线程只需要堆栈指针以及程序计数器就可以了,开销远小于创建或撤销进程时的开销。在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。

(5)通信:线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助IPC。

img

进程的五种状态:

img

(1)创建状态:进程刚被创建,尚未进入就绪队列。创建进程需要两个步骤:即为新进程分配所需要的资源和空间,设置进程为就绪态,并等待调度执行。

(2)就绪状态:进程已获得除CPU以外的所需的一切资源,一旦得到CPU资源即可运行;

(3)运行状态:进程正在处理器上面运行

(4)阻塞状态:进程正在等待某一事件而暂停运行,如等待某资源或者等待 IO 操作完成,即使CPU 空闲,该进程也不能运行;

(5)终止状态:进程达到正常结束点或因其他原因被终止,下一步将被撤销。 终止一个进程需要两个步骤:先等待操作系统或相关的进程进行善后处理;然后回收占用的资源并被系统删除。

  • 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
  • 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。

进程的调度算法:

(1)先来先服务:按照请求的顺序进行调度,使用队列实现。有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。

(2)最短作业优先:按照估计运行时间最短的顺序进行调度。有利于短作业,但长作业有可能饿死,处于一直等待短作业执行完毕的状态,因为可能一直有短作业到来,那么长作业永远得不到调度。

(3)最短剩余时间优先:按估计剩余时间最短的顺序进行调度。

(4)时间片轮转:将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。

时间片轮转算法的效率和时间片的大小有很大关系:因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。而如果时间片过长,那么实时性就不能得到保证。

(5)优先级调度:为每个进程分配一个优先级,按优先级进行调度。为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。

(6)多级反馈队列调度算法:可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。它设置了多个队列,每个队列时间片大小都不同,进程在第一个队列没执行完,就会被移到下一个队列。

实施过程:

  • ① 设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小。
  • ② 当一个新进程进入内存后,首先放入第一队列的末尾,按FCFS原则排队等候调度。 如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,在同样等待调度…… 如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行。
  • ③ 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1到第(i-1)队列空时, 才会调度第i队列中的进程运行,并执行相应的时间片轮转。
  • ④ 如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列, 则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。

进程的通信方式:

(1)匿名管道 pipe:用于具有亲缘关系的进程间的通信;

(2)有名管道 named pipe:可以用于无亲缘关系的进程间的通信,可以实现本机任意两个进程间的通信

(3)信号 signal:用于通知和接收进程某个事件已经发生。

(4)消息队列 Message Queuing :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。消息队列克服了信号传递信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。管道和消息队列的通信数据都是先进先出的原则,但消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取,比 FIFO 更有优势。

  • 无名管道:只存在于内存中的文件;
  • 命名管道:存在于实际的磁盘介质或者文件系统
  • 消息队列:存放在内核中,只有在内核重启,即操作系统重启或者显示地删除一个消息队列时,该消息队列才会被真正的删除。

(5)信号量 semophore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。

(6)共享内存 Shared memory:多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。它是针对其他进程间通信方式运行效率低而专门设计的。

(7)套接字 socket:与其他通信机制不同的是,socket 可用于不同机器间的进程通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点。

线程的七种状态:

在 JVM 中,可以将线程划分为以下几种状态:创建(new)、就绪(runnable/start)、运行(running)、阻塞(blocked)、等待(waiting)、时间等待(time waiting) 和 消亡(dead/terminated)。在某一时刻,一个线程只能处于一种状态。

线程的状态.jpg-59.9kB

线程间同步的方式:

(1)临界区 CriticalSection:在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

(2)互斥量 Mutex:采用互斥对象机制。只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。互斥对象和临界区对象非常相似,但是互斥量允许在进程间使用,而临界区只限制于同一进程的各个线程之间使用,但是更节省资源,更有效率。

(3)信号量 semophore:信号量其实就是一个计数器,限制了同一时刻访问同一资源的最大线程数。如果这个计数达到了零,则所有对这个Semaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。

(*** Event,wait/notify:事件机制,允许一个线程在处理完一个任务后,主动唤醒另一个线程执行任务,通过通知操作的方式来保持线程的同步。

死锁

在两个或以上并发进程中,如果每个进程持有某种资源并且都等待别的进程释放它们保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁,也就是多个进程无限期的阻塞、相互等待的一种状态。

1、死锁的四个必要条件:

(1)互斥条件:一个资源每次只能被一个进程使用。此时若有其他进程请求该资源,则请求进程只能等待。

(2)请求与保持条件:进程已经获得了至少一个资源,但是又提出新的资源请求,而该资源已经被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。即当一个进程等待其他进程是,继续占有已经分配的资源。

(3)不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,只能由获得该资源的进程释放。

(4)循环等待条件:若干进程间形成首尾相接的循环等待资源的关系。

2、处理死锁的基本策略:

常用的处理死锁的方法有:死锁预防、死锁避免、死锁检测、死锁解除、鸵鸟策略。

(1)死锁的预防:

基本思想就是确保死锁发生的四个必要条件中至少有一个不成立:

  • ① 破除资源互斥条件
  • ② 破除“请求与保持”条件:实行资源预分配策略,进程在运行之前,必须一次性获取所有的资源。缺点:在很多情况下,无法预知进程执行前所需的全部资源,因为进程是动态执行的,同时也会降低资源利用率,导致降低了进程的并发性。
  • ③ 破除“不可剥夺”条件:允许进程强行从占有者那里夺取某些资源。当一个已经保持了某些不可被抢占资源的进程,提出新的资源请求而不能得到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着进程已经占有的资源会被暂时被释放,或者说被抢占了。
  • ④ 破除“循环等待”条件:实行资源有序分配策略,对所有资源排序编号,按照顺序获取资源,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。

(2)死锁避免:

死锁预防通过约束资源请求,防止4个必要条件中至少一个的发生,可以通过直接或间接预防方法,但是都会导致低效的资源使用和低效的进程执行。而死锁避免则允许前三个必要条件,但是通过动态地检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态。所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。银行家算法是经典的死锁避免的算法。

(3)死锁检测:

死锁预防策略是非常保守的,他们通过限制访问资源和在进程上强加约束来解决死锁的问题。死锁检测则是完全相反,它不限制资源访问或约束进程行为,只要有可能,被请求的资源就被授权给进程。但是操作系统会周期性地执行一个算法检测前面的循环等待的条件。死锁检测算法是通过资源分配图来检测是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有存在环,也就是检测到死锁的发生。

  • (1)如果进程-资源分配图中无环路,此时系统没有死锁。
  • (2)如果进程-资源分配图中有环路,且每个资源类中只有一个资源,则系统发生死锁。
  • (3)如果进程-资源分配图中有环路,且所涉及的资源类有多个资源,则不一定会发生死锁。

(4)死锁解除:

死锁解除的常用方法就是终止进程和资源抢占,回滚。所谓进程终止就是简单地终止一个或多个进程以打破循环等待,包括两种方式:终止所有死锁进程和一次只终止一个进程直到取消死锁循环为止;所谓资源抢占就是从一个或者多个死锁进程那里抢占一个或多个资源。

(5)鸵鸟策略:

把头埋在沙子里,假装根本没发生问题。因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任何措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。

活锁:

某些情况下,当进程意识到它不能获取所需要的下一个锁时,就会尝试礼貌的释放已经获得的锁,然后等待非常短的时间再次尝试获取。可以想像一下这个场景:当两个人在狭路相逢的时候,都想给对方让路,相同的步调会导致双方都无法前进。

现在假想有一对并行的进程用到了两个资源。它们分别尝试获取另一个锁失败后,两个进程都会释放自己持有的锁,再次进行尝试,这个过程会一直进行重复。很明显,这个过程中没有进程阻塞,但是进程仍然不会向下执行,这种状况我们称之为活锁。

内存管理

1、内存连续分配算法:

为了能将用户程序装入内存,必须为它分配一定大小的内存空间。连续分配算法是最早出现的分配方式,该分配方式为用户程序在内存中分配一个连续的内存空间。连续分配方式可以分为四类:单一连续分配、固定分区分配、动态分区分配 和 动态可重定位分区分配。

(1)单一连续分配:

内存在此方式下分为系统区和用户区,系统区仅提供给操作系统使用,通常在低地址部分;用户区是为用户提供的、除系统区之外的内存空间。这种方式无需进行内存保护。

这种方式的优点是简单、无外部碎片,可以釆用覆盖技术,不需要额外的技术支持。缺点是只能用于单用户、单任务的操作系统中,有内部碎片,存储器的利用率极低。

(2)固定分区分配:

将用户内存空间划分为若干个固定大小的区域(分区大小可以相等也可以不等),每个分区只装入一道作业。 当有空闲分区时,便可以再从外存的后备作业队列中,选择适当大小的作业装入该分区,如此循环。

这种分区方式存在两个问题:一是程序可能太大而放不进任何一个分区中,这时用户不得不使用覆盖技术来使用内存空间;二是主存利用率低,当程序小于固定分区大小时,也占用了一个完整的内存分区空间,存在称为内部碎片。

(3)动态分区分配:

该分区方法不预先划分内存划,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要,因此分区的大小和数目是可变的。在进程装入主存时,如果内存中有多个足够大的空闲块,操作系统必须确定分配哪个内存块给进程使用,这就是动态分区的分配策略,常见的分配策略有:

① 首次适应算法:从空闲分区链首开始查找,直至找到一个能满足其大小需求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者。

该算法倾向于使用内存中低地址部分的空闲分区,在高地址部分的空闲分区非常少被利用,从而保留了高地址部分的大空闲区。为以后到达的大作业分配大的内存空间创造了条件。缺点在于低址部分不断被划分,留下许多内存碎片,并且每次查找都从低址部分开始,会增加查找的开销。

② 循环首次适应算法:在为进程分配内存空间时,不再每次从链首开始查找,而是从上次找到的空闲分区开始查找,直至找到一个能满足需求的空闲分区,并从中划出一块来分给作业。

该算法能使空闲中的内存分区分布得更加均匀,缺点是将会缺乏大的空闲分区。

(3)最佳适应算法:把既能满足需求,又是最小的空闲分区分配给作业。

为了加速查找,需要将所有的空闲区按照大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足需求的空闲区,必然是最优的,因为每次分配后剩余的空间一定是最小的,缺点是在于内存中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。

(4)最差适应算法:按分区大小递减的顺序形成空闲区链,分配时直接从空闲区链的第一个空闲分区中分配,如果第一个空闲分区不能满足,那么再没有空闲分区能满足需要。在大空闲区中放入程式后,剩下的空闲区常常也非常大,于是还能装下一个较大的新程式。

该算法克服了最佳适应算法留下的许多小碎片的不足,但缺点是保留大的空闲区的可能性减小了,而且空闲区回收也和最佳适应算法相同复杂。

2、虚拟内存:

连续分配方式会形成许多“碎片”,虽然可以通过“紧凑”方法将碎片拼接成可用的大块空间,但开销很大,如果允许将一个进程分散地装入到许多不相邻的分区中,便可以充分利用内存,而无须再进行“紧凑”。基于这一思想而产生了离散分配方式:分页存储管理、分段存储管理、段页式存储管理。而实现这种离散式分配的基础就是虚拟内存。

虚拟内存是为了让物理内存扩充成更大的逻辑内存,让程序获得更多的可用内存。实现的前提是应用程序在运行之前没有必要将页面或段全部装入内存,仅需将那些当前运行所需的少数页面或段先装入内存,其余部分暂留在磁盘上。

虚拟内存的基本思想是:每个程序拥有自己的地址空间,这个空间被分为大小相等的多个块,称为页,每个页都是一段连续的地址。这些页被映射到物理内存,但并不是所有的页都必须在内存中才能运行程序。当程序引用到一部分在物理内存中的地址空间时,由硬件立刻进行必要的映射;当程序引用到一部分不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的命令。

这样,对于进程而言,逻辑上似乎有很大的内存空间,实际上其中一部分对应物理内存上的一块(称为帧,通常页和帧大小相等),还有一些没加载在内存中的对应在硬盘上。

img

虚拟内存的好处在于:

  • (1)在内存中更多的进程,提高系统并发度。由于对任何特定的进程都仅仅装入它的某些块,因此就有足够的空间来放置更多的进程。
  • (2)进程可以比物理内存的全部空间还大。

3、页面置换算法:

在进程运行的过程中,如果所要访问的页面不在内存,则需把他们调入内存,但是如果内存已无空闲空间时,系统必须从内存中调出一页程序或者数据送到磁盘的对换区中。这时,把选择换出页面的算法称为页面置换算法。

(1)最佳页面替换算法(OPT):被换出的页面将是最长时间内不再被访问的,通常可以保证获得最低的缺页率。是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。

(2)最近最少使用(LRU):淘汰最近一段时间内最久未被使用的页面。

(3)最近未使用(NRU):

每个页面都有两个状态位:R 与 M,当页面被访问时设置页面的 R=1,当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类:

  • R=0,M=0
  • R=0,M=1
  • R=1,M=0
  • R=1,M=1

当发生缺页中断时,NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。

NRU 优先换出已经被修改的脏页面(R=0,M=1),而不是被频繁使用的干净页面(R=1,M=0)。

(4)先进先出(FIFO):淘汰在内存中驻留时间最长的页。

(5)第二机会算法(SCR):

FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问题,对该算法做一个简单的修改:当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候,检查最老页面的 R 位。如果 R 位是 0,那么这个页面既老又没有被使用,可以立刻置换掉;如果是 1,就将 R 位清 0,并把该页面放到链表的尾端,修改它的装入时间使它就像刚装入的一样,然后继续从链表的头部开始搜索。

(6)时钟页面替换算法(Clock):

第二次机会算法需要在链表中移动页面,降低了效率。时钟算法与SCR算法思路一致。只是用循环队列来构造页面队列,队列指针指向可能被淘汰的页面。如果队列指针指向的页的“引用位”为1,则将其置为0,同时队列指针指向下一个页。

4、颠簸/抖动:

颠簸本质上是指频繁的页调度行为,具体来讲,进程发生缺页中断,这时,必须置换某一页。然而,其他所有的页都在使用,它置换一个页,但又立刻再次需要这个页。因此,会不断产生缺页中断,导致整个系统的效率急剧下降,这种现象称为颠簸(抖动)。

内存颠簸的解决策略包括:

  • 如果是因为页面替换策略失误,可以修改替换算法来解决这个问题;
  • 如果是因为运行的程序太多,造成程序无法同时将所有频繁访问的页面调入内存,则要降低多道程序的数量;
  • 否则,还剩下两个办法:终止该进程或增加物理内存容量

文件系统

1、磁盘的物理结构:

  • 磁盘面(Platter):一个磁盘有多个盘面;
  • 磁道(Track):盘面上的圆形带状区域,一个盘面可以有多个磁道;
  • 扇区(Track Sector):磁道上的一个弧段,一个磁道可以有多个扇区,它是最小的物理储存单位,目前主要有 512 bytes 与 4 K 两种大小;
  • 磁头(Head):与盘面非常接近,能够将盘面上的磁场转换为电信号(读),或者将电信号转换为盘面的磁场(写);
  • 机械臂杆(Actuator arm):用于在磁道之间移动磁头;
  • 转轴(Spindle):使整个盘面转动。

在磁盘上定位某个物理记录需要知道其柱面号、磁头号以及扇区号。定位物理记录时,磁头到达指定扇区的时间称为查找时间, 选择磁头号并旋转至指定扇区的时间称为 搜索延迟。

2、磁盘调度算法:

读写一个磁盘块的时间的影响因素有:

  • 寻道时间(制动手臂移动,使得磁头移动到适当的磁道上)
  • 旋转时间(主轴转动盘面,使得磁头移动到适当的扇区上)
  • 实际的数据传输时间

其中,寻道时间最长,因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。

(1)先来先服务(FCFS):按照磁盘请求的顺序进行调度。优点是公平和简单,缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。

(2)最短寻道时间优先(SSTF):优先调度与当前磁头所在磁道距离最近的磁道。虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两端的磁道请求更容易出现饥饿现象。

(3)电梯算法(SCAN):电梯算法和电梯的运行过程类似,总是按一个方向来进行磁盘调度,直到该方向上没有未完成的磁盘请求,然后改变方向。因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了 SSTF 的饥饿问题。

IO

Unix 常见的IO模型:

对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:

  • 等待数据准备就绪 (Waiting for the data to be ready)
  • 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

正式因为这两个阶段,Linux系统产生了下面五种网络模式的方案:

  • 阻塞式IO模型(blocking IO model)
  • 非阻塞式IO模型(noblocking IO model)
  • IO复用式IO模型(IO multiplexing model)
  • 信号驱动式IO模型(signal-driven IO model)
  • 异步IO式IO模型(asynchronous IO model)

其中,IO多路复用模型指的是:使用单个进程同时处理多个网络连接IO,他的原理就是select、poll、epoll 不断轮询所负责的所有 socket,当某个socket有数据到达了,就通知用户进程。该模型的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

select、poll 和 epoll 之间的区别:

(1)select:时间复杂度 O(n)

select 仅仅知道有 I/O 事件发生,但并不知道是哪几个流,所以只能无差别轮询所有流,找出能读出数据或者写入数据的流,并对其进行操作。所以 select 具有 O(n) 的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

(2)poll:时间复杂度 O(n)

poll 本质上和 select 没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个 fd 对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的。

(3)epoll:时间复杂度 O(1)

epoll 可以理解为 event poll,不同于忙轮询和无差别轮询,epoll 会把哪个流发生了怎样的 I/O 事件通知我们。 所以说 epoll 实际上是事件驱动(每个事件关联上 fd)的。

select,poll,epoll 都是 IO 多路复用的机制。I/O 多路复用就是通过一种机制监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),就通知程序进行相应的读写操作。但 select,poll,epoll 本质上都是同步 I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。

计网

计算机网络模型:

TCP/IP 与 OSI 都是为了使网络中的两台计算机能够互相连接并实现通信与回应,但他们最大的不同在于,OSI 是一个理论上的网络通信模型,而 TCP/IP 则是实际上的网络通信标准。

img

一、OSI七层模型:

1、物理层:实现计算机节点之间比特流的透明传输,规定传输媒体接口的标准,屏蔽掉具体传输介质和物理设备的差异,使数据链路层不必关心网络的具体传输介质,按照物理层规定的标准传输数据就行

2、数据链路层:通过差错控制、流量控制等方法,使有差错的物理线路变为无差错的数据链路。

数据链路层的几个基本方法:数据封装成桢、透明传输、差错控制、流量控制。

  • 封装成桢:把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。
  • 透明传输:零比特填充、转义字符。
  • 差错控制:接收者检测错误,如果发现差错,丢弃该帧,差错控制方法有 CRC 循环冗余码
  • 流量控制:控制发送的传输速度,使得接收方来得及接收。传输层TCP也有流量控制功能,但TCP是端到端的流量控制,链路层是点到点(比如一个路由器到下一个路由器)

3、网络层:实现网络地址与物理地址的转换,并通过路由选择算法为分组通过通信子网选择最适当的路径

网络层最重要的一个功能就是:路由选择。路由一般包括路由表和路由算法两个方面。每个路由器都必须建立和维护自身的路由表,一种是静态维护,也就是人工设置,适用于小型网络;另一种就是动态维护,是在运行过程中根据网络情况自动地动态维护路由表。

4、传输层:提供源端与目的端之间提供可靠的透明数据传输,传输层协议为不同主机上运行的进程提供逻辑通信。

  • 网络层协议负责的是提供主机间的逻辑通信;
  • 传输层协议负责的是提供进程间的逻辑通信。

5、会话层:是用户应用程序和网络之间的接口,负责在网络中的两节点之间建立、维持、终止通信。

6、表示层:处理用户数据的表示问题,如数据的编码、格式转换、加密和解密、压缩和解压缩。

7、应用层:为用户的应用进程提供网络通信服务,完成和实现用户请求的各种服务。

二、TCP/IP模型

TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含了一系列构成互联网基础的网络协议,是Internet的核心协议。TCP/IP协议族按照层次由上到下,层层包装。

img

上图表示了TCP/IP协议中每个层的作用,而TCP/IP协议通信的过程其实就对应着数据入栈与出栈的过程。入栈的过程,数据发送方每层不断地封装首部与尾部,添加一些传输的信息,确保能传输到目的地。出栈的过程,数据接收方每层不断地拆除首部与尾部,得到最终传输的数据。

img

网络层:


实现网络地址与物理地址的转换,并通过路由选择算法为分组通过通信子网选择最适当的路径

1、IP地址与物理地址:

物理地址是数据链路层和物理层使用的地址,IP地址是网络层和以上各层使用的地址,是一种逻辑地址,其中ARP协议将IP地址转换成物理地址。

2、ARP地址解析协议的工作原理:

ARP 是根据 IP 地址获取 MAC 地址的一种协议,核心原理就是广播发送ARP请求,单播发送ARP响应

  • (1)每个主机都在自己的ARP缓冲区中建立一个ARP列表,以表示 IP 地址和 MAC 地址之间的对应关系。
  • (2)当源主机要发送数据时,先检查ARP列表中是否有该 IP 地址对应的 MAC 地址,如果有,则直接发送数据;如果没有,就向本网段的所有主机发送ARP数据包,用于查询目的主机的MAC地址,该数据包包括的内容有:源主机IP地址,源主机MAC地址,目的主机的IP。
  • (3)当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。
  • (4)源主机收到 ARP 响应包后,将目的主机的 IP 和 MAC 地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败

3、RARP逆地址解析协议:

RARP是逆地址解析协议,作用是完成硬件地址到IP地址的映射,主要用于无盘工作站,因为给无盘工作站配置的IP地址不能保存。工作流程:在网络中配置一台RARP服务器,里面保存着 MAC 地址和 IP 地址的映射关系,当无盘工作站启动后,就封装一个RARP数据包,里面有其MAC地址,然后广播到网络上去,当服务器收到请求包后,就查找对应的MAC地址的IP地址装入响应报文中发回给请求者。因为需要广播请求报文,因此RARP只能用于具有广播能力的网络。

4、DHCP协议:

动态主机配置协议,对 IP地址进行集中管理和分配,提升地址的使用率,通过DHCP协议,可以使客户机自动获得服务器分配的lP地址和子网掩码

img

5、ICMP协议:

因特网控制报文协议,用于在IP主机、路由器之间传递控制消息(控制消息是指网络通不通、主机是否可达、路由器是否可用等网络本身的消息),确认 IP 包是否成功到达目标地址。因为 IP 协议并不是一个可靠的协议,它不保证数据被送达,当传送IP数据包发生错误,比如主机不可达、路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机,给主机一个处理错误的机会。

ICMP报文有两种:差错报告报文和询问报文。以下是4种常见的ICMP差错报告报文

img

6、交换机与路由器的区别:

  • (1)工作所处的OSI层次不一样,交换机工作在OSI第二层数据链路层,路由器工作在OSI第三层网络层;
  • (2)寻址方式不同:交换机根据MAC地址寻址,路由器根据IP地址寻址;
  • (3)转发速不同:交换机的转发速度快,路由器转发速度相对较慢。

7、路由选择协议:

(1)内部网关协议IGP:

  • ① RIP(Routing Information Protocol):是一种动态路由选择协议,基于距离矢量算法,使用“跳数”来衡量到达目标地址的路由距离,并且只与自己相邻的路由器交换信息,范围限制在15跳之内。
  • ② OSPF:开放最短路径优先协议,使用Dijskra算法计算出到达每一网络的最短路径,并在检测到 链路的情况发生变化时(如链路失效),就执行该算法快速收敛到新的无环路拓扑。

(2)外部网关协议:

BGP:边界网关协议,BGP 是力求寻找一条能够到达目的网络 且 较好的路由,而并非要寻找一条最佳路由。BGP采用路径向量路由选择协议。

传输层:


传输层主要提供不同主机上进程间 逻辑通信 + 可靠传输 或者 不可靠传输的功能。

一、TCP 和 UDP:

1、TCP 和 UDP的区别?

(1)TCP是面向字节流的,基本传输单位是TCP报文段;UDP是面向报文的,基本传输单位是是用户数据报;

  • 面向字节流:应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。
  • 面向报文:面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送。因此,应用程序必须选择合适大小的报文。

(2)TCP 注重安全可靠性,连接双方在进行通信前,需进行三次握手建立连接。UDP 是无连接的,使用最大努力交付,即不保证可靠交付。

(3)UDP 不需要连接等待,所以数据传输快,而 TCP 传输效率相对较低

(4)TCP首部开销是20个字节;UDP的首部开销是8个字节,这也是减少网络传输开销的一方面

(5)TCP有拥塞控制和流量控制,而UDP没有拥塞控制和流量控制

(6)TCP支持点对点通信,提供全双工通信,不提供广播或多播服务;UDP支持一对一、一对多、多对一、多对多的通信模式。

2、TCP 和 UDP 的适用场景:

(1)当对网络通讯质量要求不高时,并且要求网络通讯速度能尽量的快,这时就可以使用UDP。比如即使通信: 语音、 视频 、直播等

(2)当对网络通讯质量有要求时,要求整个数据准确无误可靠的传递给对方,这时就适用使用 TCP 协议,一般用于文件传输、发送和接收邮件等场景。比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议都是使用 TCP 协议

① TCP对应的协议:

  • FTP:文件传输协议,使用21端口
  • Telnet:远程终端接入,使用23端口,用户可以以自己的身份远程连接到计算机上,可提供基于DOS模式下的通信服务。
  • SMTP:邮件传送协议,用于发送邮件,使用25端口
  • POP3:邮件传送协议,P用于接收邮件。使用110端口
  • HTTP:万维网超文本传输协议,是从Web服务器传输超文本到本地浏览器的传送协议

② UDP对应的协议:

  • DNS:域名解析服务,将域名地址转换为IP地址,使用53号端口;
  • SNMP:网络管理协议,用来管理网络设备,使用161号端口;
  • TFTP:简单文件传输协议,提供不复杂、开销不大的文件传输服务,使用 69 端口;
  • NFS:远程文件服务器
  • RIP:路由信息协议
  • DHCP:动态主机配置协议
  • IGMP:网际组管理协议

3、TCP的首部字段:

img

(1)源端口和目的端口:分别占16位,指发送方应用程序的端口和目的方应用程序的端口号,通过 IP 地址 + 端口号就可以确定一个进程地址

(2)序号(Sequense Number,SN):在一个TCP连接中传送的字节流中的每一个字节都按顺序编号,该字段表示本报文段所发送数据的第一个字节的序号。(初始序号称为 Init Sequense Number, ISN)

例如,一报文段的序号是 101,共有 100 字节的数据。这就表明:本报文段的数据的第一个字节的序号是 101,最后一个字节的序号是 200。显然,下一个报文段的数据序号应当从 201 开始,即下一个报文段的序号字段值应为 201。

(3)确认号 ack:期望收到对方下一个报文段的第一个数据字节的序号。若确认号为 N,则表明:到序号 N-1 为止的所有数据都已正确收到。

(4)头部长度:指出 TCP报文段的数据起始处 距离 TCP报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。

(5)保留位:占6位,应置为 0,保留为今后使用。

(6)6个控制位:用于说明该报文段的性质:

  • ① 紧急位URG:当 URG = 1 时,表明此报文段中有紧急数据,是高优先级的数据,应尽快发送,不用在缓存中排队。
  • ② 确认ACK:仅当 ACK = 1 时确认号字段才有效,当 ACK = 0 时确认号无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置为 1。
  • ③ 推送PSH:接收方收到 PSH = 1 的报文段时,就直接发送给应用进程,而不用等到整个缓冲区都填满了后再向上传送。
  • ④ 复位RST:当 RST = 1 时,表明 TCP 连接中出现了严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立传输连接。
  • ⑤ 同步SYN:SYN = 1 表示这是一个连接请求或连接接受报文。当 SYN = 1 而 ACK = 0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN = 1 且 ACK = 1。
  • ⑥ 终止FIN:用来释放一个连接。当 FIN = 1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。

(7)窗口大小:16位,用于控制发送端的滑动窗口大小

(8)校检和:16位,校验数据段是否未被修改

(9)紧急指针:16位。

二、TCP连接的建立与断开:

1、建立连接的三次握手:

img

(1)第一次握手:客户端向服务端发送一个 SYN 报文(SYN = 1),并指明客户端初始化序列号 ISN,即seq = x,表示本报文所发送的第一个字节的序号。此时客户端处于 SYN_Sent 状态,等待服务端确认。

三次握手的一个重要功能是客户端和服务端交换 ISN,以便让对方知道接下来接收数据时如何按序列号组装数据。

ISN 是动态生成的,并非固定,因此每个连接都将具有不同的 ISN。如果 ISN 是固定的,攻击者很容易猜出后续的确认号。

(2)第二次握手:服务端收到数据包后,由 SYN = 1 知道客户端请求建立连接,那么就会对这个TCP 连接分配缓存和变量(缓存指的是一个字节流队列),接着返回一个确认报文:设置 SYN = 1,ACK = 1,同时指定自己的初始化序列号 ISN,即图中的 seq = y,并把客户端的 ISN + 1 作为确认号 ack 的值,表示已经收到了客户端发来的的 SYN 报文,希望收到的下一个数据的第一个字节的序号是 x + 1,此时服务端进入SYN_REVD状态。

(3)第三次握手:客户端收到确认后,检查ACK是否为1,ack是否为 x +1,如果正确,则给服务端发送一个 ACK 报文:设置 ACK = 1,把服务端的 ISN + 1 作为 ack 的值,表示已经收到了服务端发来的 SYN 报文,希望收到的下一个数据的第一个字节的序号是 y + 1,并指明此时客户端的序列号 seq = x + 1,此时客户端和服务器端都进入 ESTABLISHED 状态。完成三次握手,随后Client与Server之间可以开始传输数据了。

此时 SYN 控制位变为 0,表示这不是建立连接的请求了,要正式发数据了。

2、为什么不能用两次握手进行建立连接?

(1)三次握手目的是确认双方的接收与发送能力是否正常,同步连接双方的初始化序列号 ISN,为后面的可靠性传输做准备。而两次握手只有服务端对客户端的起始序列号做了确认,但客户端却没有对服务端的初始序列号做确认,不能保证传输的可靠性。

(2)三次握手可以防止已失效的连接请求报文段突然又传送到了服务端,导致服务器错误地建立连接,浪费服务端的连接资源。

客户端发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达Server。本来这是一个早已失效的报文段,但Server收到此失效的连接请求报文段后:

① 假设不采用“三次握手”,那么只要Sever发出确认,新的连接就建立了。但由于现在Client并没有发出建立连接的请求,因此不会理睬Server的确认,也不会向Server发送数据。而Server却以为新的连接已经建立,并一直等待Client发来数据,这样,Server的很多资源就白白浪费掉了

② 而采用“三次握手”协议,只要Server收不到来自Client的确认,就知道Client并没有要求建立请求,就不会建立连接了。

3、断开连接的四次挥手:

img

(1)第一次挥手:客户端发送一个 FIN 报文,设置 FIN = 1 并指定序列号 seq = u(u 是之前传送过来的最后一个字节的序号 + 1),主动关闭 TCP 连接,此时客户端进入FIN_WAIT_1状态;

(2)第二次挥手:服务端收到 FIN 报文后,由FIN=1 知道客户端请求关闭连接,则返回确认报文:设置ACK = 1,ack = u + 1,seq = v(v 的值取决于服务器发送给客户端之前的一个包确认号是多少)

  • 服务端进入CLOSE_WAIT状态,此时TCP连接处于半关闭状态,即客户端不能向服务端发送报文,只能接收,但服务端仍然可以向客户端发送数据。
  • 客户端收到服务端的确认后,进入 FIN_WAIT2 状态,等待服务端发出的连接释放报文段。

(3)第三次挥手:当服务端没有要向客户端发送的数据时,就向客户端发送一个 FIN 报文,设置 FIN = 1 并指定序列号 seq = w(w 的值取决于服务器发送给客户端之前的一个包确认号是多少),用于关闭服务端到客户端的数据传送。此时服务器处于 LAST_ACK 状态

(4)第四次挥手:客户端收到 FIN 报文后,发送给服务端一个 ACK 报文作为应答:设置 ACK=1 和 ack = w +1。发送之后,客户端处于 TIME_WAIT状态,如果服务端接收到这个数据包,则进入CLOSED状态,完成四次挥手。

4、为什么需要 TIME_WAIT 状态:

TIME_WAIT 状态持续 2MSL(最大报文存活时间),约4分钟才转换成CLOSE状态。由于TIME_WAIT 的时间会非常长,因此服务端应尽量减少主动关闭连接,TIME_WAIT 的主要作用有:

(1)重发丢失的 ACK 报文,保证连接可靠的关闭:

由于网络等原因,无法保证最后一次挥手的 ACK 报文一定能传送给对方,如果 ACK 丢失,对方会超时重传 FIN,主动关闭端会再次响应ACK过去;如果没有 TIME_WAIT 状态,直接关闭,对方重传的FIN报文则被响应一个RST报文,此RST会被动关闭端被解析成错误。同时,服务器就因为接收不到客户端的信息而无法正常关闭。

(2)保证本次连接的重复数据段从网络中消失:

如果存在两个连接,第一个连接正常关闭,第二个相同的连接紧接着建立;如果第一个连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达,则会干扰第二连接,等待 2MSL 可以让上次连接的报文数据消逝在网络中。

5、为什么需要四次挥手:

TCP 是全双工模式,并且支持半关闭特性,提供了连接的一端在结束发送后还能接收来自另一端数据的能力。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。

通俗的来说,两次握手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次握手。

6、什么是SYN洪泛:

SYN 洪泛是指利用 TCP 需要三次握手的特性,攻击者伪造 SYN 报文向服务器发起连接,服务器在收到报文后用 ACK 应答,但之后攻击者不再对该响应进行应答,造成一个半连接。假设攻击者发送大量这样的报文,那么被攻击主机就会造成大量的半连接,耗尽其资源,导致正常的 SYN 请求因为队列满而被丢弃,使得正常用户无法访问。

半连接队列:服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把这种状态下的请求连接放在一个队列里,我们把这种队列称之为半连接队列。当然还有一个全连接队列,完成三次握手后建立起的连接就会放在全连接队列中。

7、三次握手过程中是否可以携带数据:

第三次握手时是可以携带数据的,但第一二次握手时不可以携带数据。

(1)假如第一次握手可以携带数据的话,那么会放大 SYN 洪泛。如果有人要恶意攻击服务器,每次都在第一次握手中的 SYN 报文中放入大量的数据,然后疯狂重复发送 SYN 报文的话,就会让服务器开辟大量的缓存来接收这些报文,内存会很容易耗尽,从而拒绝服务。

(2) 第三次握手时客户端已经处于 ESTABLISHED 状态,对于客户端来说,他已经建立起连接了,并且已经知道服务器的接收和发送能力是正常的,所以也就可以携带数据了。

8、TCP的粘包和拆包:

程序需要发送的数据大小和TCP报文段能发送MSS(Maximum Segment Size,最大报文长度)是不一样的。大于MSS时,就需要把程序数据拆分为多个TCP报文段,称之为拆包;小于时,则要考虑合并多个程序数据为一个TCP报文段,则是粘包;其中MSS = TCP报文段长度-TCP首部长度。在IP协议层或者链路层、物理层,都存在拆包、粘包现象。

解决粘包和拆包的方法主要有:

  • (1)在数据尾部增加特殊字符进行分割;
  • (2)将数据定为固定大小;
  • (3)将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小

三、TCP可靠性传输:

1、TCP 如何保证可靠性传输:

  • (1)三次握手
  • (2)应答机制与超时重传:TCP接收端收到发送端的数据时,它将发送一个确认。当TCP发送端发出一个报文段后,它会启动一个定时器,等待接收端的确认报文段,如果不能及时收到一个确认,将重发这个报文段。
  • (3)数据包校验与丢弃重复数据:TCP会检测数据在传输过程中的任何变化,若校验出包有错,则丢弃报文段并且不给出响应,这时TCP会超时重发数据;对于重复数据,则进行丢弃;
  • (4)对失序数据包进行重排序:既然TCP报文段作为IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对失序数据进行重新排序,然后才交给应用层;
  • (5)流量控制:TCP 连接的每一方都有固定大小的缓冲空间。TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据,防止较快主机致使较慢主机的缓冲区溢出。TCP使用的流量控制协议是可变大小的滑动窗口协议。
  • (6)拥塞控制:网络拥塞时,减少数据的发送。

2、TCP的流量控制:

所谓流量控制就是让发送方的发送速率不要太快,让接收方来得及接收。因为如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。TCP的流量控制是通过大小可变的滑动窗口来实现的。接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK报文来通知发送端,滑动窗口是接收端用来控制发送端发送数据的大小,从而达到流量控制

其实发送方的窗口上限,是取值拥塞窗口和滑动窗口两者的最小值。当滑动窗口为 0 时,发送方一般不能再发送数据包,但有两种情况除外,一种情况是可以发送紧急数据,例如,允许用户终止在远端机上的运行进程。另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。

设A向B发送数据。在连接建立时,B告诉了A:“我的接收窗口是 rwnd = 400 ”(这里的 rwnd 表示 receiver window) 。因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值。假设每一个报文段为100字节长,而数据报文段序号的初始值设为1。

img

从图中可以看出,B进行了三次流量控制。第一次把窗口减少到 rwnd = 300 ,第二次又减到了 rwnd = 100 ,最后减到 rwnd = 0 ,即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机B重新发出一个新的窗口值为止。B向A发送的三个报文段都设置了 ACK = 1 ,只有在 ACK=1 时确认号字段才有意义。

3、TCP的拥塞控制:

拥塞控制就是防止过多的数据注入网络中,使网络中的路由器或链路不致过载。发送方维持一个拥塞窗口cwnd 的状态变量。拥塞窗口的大小动态变化,取决于网络的拥塞程度,发送方让自己的发送窗口等于拥塞窗口。只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。 拥塞控制的方法主要有以下几种:慢启动、拥塞避免、快重传和快恢复。

(1)慢开始算法:当发送主机开始发送数据时,不要一开始就发送大量的数据,因为不清楚网络的拥塞情况,而是试探一下网络的拥塞情况,由小到大逐渐增大发送窗口。在开始发送报文段时先设置cwnd=1,使得发送方在开始时只发送一个报文段,然后每经过一个传输轮次RTT,拥塞窗口 cwnd 就加倍。另外,为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限 ssthresh 状态变量。

当 cwnd < ssthresh 时,使用上述的慢开始算法。

当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。

当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。

(2)拥塞避免算法:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。

无论在慢开始阶段还是在拥塞避免阶段,只要网络出现拥塞(其根据就是没有收到确认),就要把慢开始门限ssthresh设置为出现拥塞时的拥塞窗口值的一半(但不能小于2)。然后把拥塞窗口cwnd 设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的数据量,使得发生拥塞的路由器有足够时间把队列中积压的数据处理完毕。过程图如下:

img

(3)快重传:快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(使发送方及早知道有报文段没有到达对方)而不必等到自己发送数据时捎带确认。发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。

img

接收方收到了M1和M2后都分别发出了确认。现在假定接收方没有收到M3但接着收到了M4。显然,接收方不能确认M4,因为M4是收到的失序报文段。根据可靠传输原理,接收方可以什么都不做,也可以在适当时机发送一次对M2的确认。但按照快重传算法的规定,接收方应及时发送对M2的重复确认,这样做可以让 发送方及早知道报文段M3没有到达接收方。发送方接着发送了M5和M6。接收方收到这两个报文后,也还要再次发出对M2的重复确认。这样,发送方共收到了 接收方的四个对M2的确认,其中后三个都是重复确认。

(4)快恢复:与快重传配合使用的还有快恢复算法,当发送方连续收到三个重复确认时,就执行“乘法减少”算法,把ssthresh门限设置为拥塞窗口cwnd的一半,但是接下去并不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法:因为如果网络出现拥塞的话,就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞,所以此时并不执行慢开始算法,而是执行拥塞避免算法。

img

4、拥塞控制和流量控制的差别:

(1)相同点:拥塞控制和流量控制的相同点都是控制丢包现象,实现机制都是让发送方发得慢一点。

(2)不同点:

① 拥塞控制是一个全局性的过程,防止过多的数据注入到网络中,造成网络拥塞

② 流量控制指点对点通信量的控制,要做的就是控制发送端发送数据的速率,以便使接收端来得及接受。

应用层:


应用层主要提供应用进程间的网络通信服务,完成用户请求的各种服务。

一、http协议:

http协议即超文本传输协议,基于TCP协议,用于从Web服务器传输超文本到本地浏览器的传送协议。http协议是无状态协议,自身不对请求和响应直接的通信状态进行保存,但有些场景下我们需要保存用户的登陆信息,所以引入了cookie 和 session 来管理状态。

1、cookie 和 session 的区别:

(1)保存位置与安全性:cookie保存在客户端,session保存在服务端,所以在安全性上面,cookie存在安全隐患,可以通过拦截或本地文件找到cookie后进行攻击,而session相对更加安全。因此,可以将登陆信息等重要信息存放为session中;其他信息如果需要保留,可以放在cookie中。

(2)存储容量:单个cookie最大只允许4KB,一个站点最多保存20个Cookie;session没有大小限制,个数只跟服务器的内存大小有关。

(3)有效期与实现机制:cookie可长期有效存在;session依赖于cookie,过期时间默认为-1,只需关闭窗口该 session 就会失效。每个客户端对应一个session ,客户端之间的 session 相互独立;

  • cookie:cookie是一小段的文本信息,当客户端请求服务器时,如果服务器需要记录该用户状态,就在响应头中向客户端浏览器颁发一个Cookie,而客户端浏览器会把cookie保存起来。当再次请求该网站时,浏览器把请求的网站连同该cookie一起提交给服务器,服务器会检查该cookie,以此来辨认用户状态。
  • session:当客户端请求服务器时,都会带上cookie,cookie里面一般都会有一个JSESSIONID,服务器就按照 JSESSIONID 来找到对应的 session;如果客户端请求不包含 JSESSIONID,则为此客户端创建session并生成相关联的JSESSIONID,并将这个JSESSIONID在本次响应中返回给客户端保存。客户端保存这个 JSESSIONID 的方式可以使用cookie机制。若浏览器禁用Cookie的话,可以通过 URL重写机制 将JSESSIONID传回服务器。

2、一个完整的http请求是怎么样?即从输入网址到获得页面的过程:

(1)解析url,获取 url 中包含的域名;

(2)通过DNS系统查询域名对应的IP;

img

DNS服务器大致分为三种类型:根DNS服务器、顶级域DNS服务器 和 权威DNS服务器,其中: 顶级域DNS服务器主要负责诸如com、org、net、edu、gov 等顶级域名。

根DNS服务器存储了所有 顶级域DNS服务器的 IP 地址,可以通过根服务器找到顶级域服务器(例如:www.baidu.com,根服务器会返回所有维护 com 这个顶级域服务器的 IP 地址)。然后你任选其中一个顶级域服务器发送请求,该顶级域服务器拿到域名后能够给出负责当前域的权威服务器地址(以 baidu为例的话,顶级域服务器将返回所有负责 baidu 这个域的权威服务器地址)。接着任选其中一个权威服务器地址查询 「www.baidu.com」 的具体 IP 地址,最终权威服务器会返回给你具体的 IP 地址。此外,本地 DNS 服务器是具有缓存功能的,通常两天内的记录都会被缓存。

所以,通过DNS系统查询域名对应的 IP 的具体步骤可以总结为:

  • ① 操作系统先查本地 hosts文件 中是否有记录,如果有,则直接返回相对应映射的IP地址。
  • ② 如果本地hosts文件中没有配置,则主机向自己的本地 DNS 服务器 发送查询报文,如果本地DNS服务器缓存中有,将直接返回结果
  • ③ 如果本地服务器缓存中没有,则从内置在内部的根服务器列表(全球13台,固定的IP地址)中选一个发送查询报文
  • ④ 根服务器解析域名中的后缀名,告诉本地服务器负责该后缀名的所有顶级服务器列表
  • ⑤ 本地服务器选择其中一个顶级域服务器发送查询请求,顶级域服务器拿到域名后继续解析,返回对应域的所有权威服务器列表
  • ⑥ 本地服务器再向返回的权威服务器发送查询报文,最终会从某一个权威服务器上得到具体的 IP 地址
  • ⑦ 主机返回结果IP

(3)浏览器得到域名对应的IP地址之后,向服务器发起三次握手请求建立TCP链接;

(4)TCP链接链接建立起来后,浏览器向服务器发送http请求,如果 html文件在缓存里,浏览器则直接返回, 如果没有,则去后台拿;

  • ① 浏览器首次加载资源成功时,服务器返回200,此时浏览器不仅将资源下载下来,而且把response的header(里面的date属性非常重要,用来计算第二次相同资源时当前时间和date的时间差)一并缓存;
  • ② 下一次加载资源时,首先要经过强缓存的处理,cache-control的优先级最高,比如cache-control:no-cache,就直接进入到协商缓存的步骤了,如果cache-control:max-age=xxx,就会先比较当前时间和上一次返回200时的时间差,如果没有超过max-age,命中强缓存,不发请求直接从本地缓存读取该文件(这里需要注意,如果没有cache-control,会取expires的值,来对比是否过期),过期的话会进入下一个阶段,协商缓存
  • ③ 协商缓存阶段,则向服务器发送header带有If-None-Match和If-Modified-Since的请求,服务器会比较Etag,如果相同,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件带上新的Etag值并返回200;
  • ④ 协商缓存第二个重要的字段是,If-Modified-Since,如果客户端发送的If-Modified-Since的值跟服务器端获取的文件最近改动的时间,一致则命中协商缓存,返回304;不一致则返回新的last-modified和文件并返回200;

(5)服务器接收到请求后,根据路径参数映射到特定的处理器进行处理,并将处理结果以及相应的视图返回给浏览器。

(6)浏览器解析视图,并根据请求到的资源、数据进行渲染页面,最终向用户呈现一个完整的页面。

  • 构建DOM树(DOM tree):从上到下解析HTML文档生成DOM节点树(DOM tree),也叫内容树(content tree);
  • 构建CSSOM(CSS Object Model)树:加载解析样式生成CSSOM树;
  • 执行JavaScript:加载并执行JavaScript代码(包括内联代码或外联JavaScript文件);
  • 构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree);
  • 渲染树:按顺序展示在屏幕上的一系列矩形,这些矩形带有字体,颜色和尺寸等视觉属性。
  • 布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置;
  • 绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成;

3、http的长连接和短连接?

http的长连接和短连接本质上是TCP长连接和短连接。从http1.1开始就默认使用长连接。

短链接是指客户端与服务端每进行一次请求操作,就建立一次TCP连接,收到服务器响应后,就断开连接。

长连接是指客户端和服务建立TCP连接后,它们之间的连接会持续存在,不会因为一次HTTP请求后关闭,后续的请求也是用这个连接进行通信,使用长连接的HTTP协议,会在响应头有加入:Connection:keep-alive。长连接可以省去每次TCP建立和关闭的握手和挥手操作,节约时间提高效率。但在长连接下,客户端一般不会主动关闭连接,如果客户端和服务端之间的连接一直不关闭的话,随着连接数越来越多,会对服务端造成压力。

所以长连接多用于频繁请求资源,而且连接数不能太多的情况,例如数据库的连接用长连接。而像Web网站这种并发量大,但是每个用户无需频繁操作的场景,一般都使用短连接,因为长连接对服务端来说会耗费一定的资源。

4、http的断点续传是如何实现的?

HTTP请求头有个Range字段;我们下载文件的时候如果遇到网络中断,如果重头开始下载会浪费时间,所以我们可以从上一次中断处继续开始下载;具体的操作:

Range: bytes=5001-10000

或者指定5001以后的所有数据

Range: bytes=5001-

5、http存在的问题:

  • 通信使用明文不加密,通信内容可能被窃听;
  • 无法验证报文的完整性,数据内容可能被篡改
  • 不验证通信方身份、可能遭到伪装,无法保证数据发送到正确的机器上;

为了解决上述几个问题,那么就引入了https协议。

二、https协议:

https 是基于tcp协议,在http的基础上加入了SSL/TLS,可看成是添加了加密和认证机制的http,使用对称加密、非对称加密、证书等技术进行进行客户端与服务端的数据加密传输,最终达到保证整个通信的安全性。

对称加密指加密和解密都使用同一个密钥的方式,这种方式存在如何安全地将密钥发送对方的问题;非对称加密使用两个密钥,公钥加密则需要私钥解密,私钥加密则需要公钥解密。不能私钥加密,私钥解密。非对称加密不需要发送用来解密的私钥,所以可以保证安全性,但是和对称加密比起来,速度非常的慢,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。

1、https的认证加密过程?如何保证内容不会被篡改的?

  • (1)https是基于tcp协议的,首先客户端会和服务端发起链接建立
  • (2)服务端返回它的证书给客户端,证书中包含了服务端公钥S.pub、颁发机构和有效期等信息
  • (3)客户端通过浏览器内置的根证书(内部包含CA机构的公钥C.pub)验证证书的合法性
  • (4)客户端生成随机的对称加密密钥Z,然后通过服务端的公钥S.pub加密发送给服务端
  • (5)客户端和服务端之后就通过对称加密密钥Z加密数据来进行http通信

2、根证书如何保证签发的证书是安全有效的?

  • (1)服务器会预先生成非对称加密密钥,私钥S.pri自己保留,而公钥S.pub则发送给CA进行签名认证
  • (2)CA机构也会预先生成非对称加密密钥,其私钥C.pri用来对服务器的公钥S.pub进行签名,生成CA证书
  • (3)CA机构将签名生成的CA证书返回给服务器,也就是前面服务端给客户端那个证书
  • (4)因为CA机构比较权威,所以很多浏览器会内置包含它公钥C.pub的证书,称之为根证书,然后可以使用根证书来验证其颁发证书的合法性了

img

在整个过程中,一共涉及2对公私密钥对,一对由服务器产生,主要用于加密,一对由CA产生,主要用于签名。

3、为什么需要CA证书认证机构呢?

CA证书是为了确保服务端的公钥是准确无误、没有被修改过的。虽然https是加密的,但是请求还是可以被拦截的,假设没有CA证书,如果服务器返回的包含公钥的包被攻击者截取,然后攻击者也生成一对公私钥,他将自己的公钥发给客户端。攻击者得到客户端数据后进行解密,然后再通过服务器的公钥加密发给服务器,这样数据就被攻击者获取到了。

有了CA证书后,客户端根据内置的CA根证书,很容易识别出攻击者的公钥不合法,或者说攻击者的证书不合法。

证书通常包含这些内容:(1) 服务端的公钥;(2) 证书发行者(CA)对证书的数字签名;(3) 证书所用的签名算法;(4) 证书发布机构、有效期、所有者的信息等其他信息

三、http 的请求与响应:

1、http的常见请求方式:

  • (1)get:向服务端获取资源,所以查询操作一般用get
  • (2)post:向服务端提交请求字段,创建操作使用 post,该操作不是幂等的,多次执行会导致多条数据被创建
  • (3)put:修改指定URL的资源,如果资源不存在,则进行创建,修改操作一般使用 put,在http中,put 被定义成幂等的,多次操作会导致前面的数据被覆盖
  • (4)patch:局部修改URL所在资源的数据,是对put的补充
  • (5)delete:删除指定URL的资源。
  • (6)head:获取响应报文的首部,即获得URL资源的头部
  • (7)options:询问服务器支持哪些方法,响应头中返回 Allow: GET、POST、HEAD
  • (8)trace:追踪路径,主要用于测试或诊断;在请求头中在Max-Forwards字段设置数字,每经过一个服务器该数字就减一,当到0的时候就直接返回,一般通过该方法检查请求发送出去是否被篡改

2、get和 post 请求的区别:

  • (1)功能:get一般用来从服务器上面获取资源,post一般用来更新服务器上面的资源。
  • (2)幂等性:get 是幂等的,post 为非幂等的
  • (3)安全性:get 请求的参数会明文附加在URL之后,而 post 请求提交的数据则被封装到请求体中,相对更安全。
  • (4)传输数据量的大小:get请求允许发送的数据量比较小,大多数浏览器都会限制请求的url长度在2048个字节,而大多数服务器最多处理64K大小的url;而post请求提交的数据量则是没有大小限制的。
  • (5)参数的数据类型:GET只接受ASCII字符,而POST没有限制。
  • (6)GET在浏览器回退时是无害的,而POST会再次提交请求。
  • (7)get请求可以被缓存,可以被保留在浏览器的历史记录中;post请求不会被缓存,不会被保留在浏览器的历史记录中。

3、http报文头分析:

(1)报文类型:报文类型分为请求报文和响应报文

① 请求报文包含三部分:

  • 请求行:包含请求方法、URI、HTTP版本信息
  • 请求首部字段
  • 请求内容实体

② 响应报文包含三部分:

  • 状态行:包含HTTP版本、状态码、状态码的原因短语
  • 响应首部字段
  • 响应内容实体

img

(2)报文中各部分的简要描述:

  • 方法(method):客户端希望服务器对资源执行的动作,是一个单独的词,比如:get 或者 post
  • 请求URL(request-URL):请求URL是资源的绝对路径,服务器可以假定自己是URL的主机/端口
  • 版本(version):报文所使用的Http版本,其格式:HTTP/<主要版本号>.<次要版本号>
  • 状态码(status-code):标识请求过程中所发生的情况
  • 原因短语(reason-phrase):数字状态码的可读版本,包含行终止序列之前的所有文本。
  • 请求头部(header):可以有零个或多个头部,每个首部都包含一个名字,后面跟着一个冒号(:),然后是一个可选的空格,接着是一个值,最后是一个CRLF首部是由一个空行(CRLF)结束的,表示了头部列表的结束和实体主体部分的开始
  • 实体的主体部分(entity-body):实体的主体部分包含一个由任意数据组成的数据块,并不是所有的报文都包含实体的主体部分,有时,报文只是以一个CRLF结束。

(3)通用头部:既可以出现在请求报文中,也可以出现在响应报文中,它提供了与报文相关的最基本的信息:

  • Connection:允许客户端和服务器指定与请求/响应连接有关的选项,http1.1之后默认是 keep-alive
  • Date:日期和时间标志,说明报文是什么时间创建的
  • Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
  • Cache-Control:用于随报文传送缓存指示

(4)请求头部:请求头部是只在请求报文中有意义的头部。用于说明是谁或什么在发送请求、请求源自何处,或者客户端的喜好及能力

  • Host:给出了接收请求的服务器的主机名和端口号
  • Referer:提供了包含当前请求URI的文档的URL
  • User-Agent:将发起请求的应用程序名称告知服务器
  • Accept:告诉服务器能够发送哪些媒体类型
  • Accept-Encoding:告诉服务器能够发送哪些编码方式
  • Accept-Language:告诉服务器能够发送哪些语言
  • Range:如果服务器支持范围请求,就请求资源的指定范围
  • If-Range:允许对文档的某个范围进行条件请求
  • Authorization:包含了客户端提供给服务器,以便对其自身进行认证的数据
  • Cookie:客户端用它向服务器传送数据

(5)响应头部:响应头部为客户端提供了一些额外信息,比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令

  • Age:(从最初创建开始)响应持续时间
  • Server:服务器应用程序软件的名称和版本
  • Accept-Ranges:对此资源来说,服务器可接受的范围类型
  • Set-Cookie:在客户端设置数据,以便服务器对客户端进行标识

(6)实体首部:描述主体的长度和内容,或者资源自身

  • Allow:列出了可以对此实体执行的请求方法
  • Location:告知客户端实体实际上位于何处,用于将接收端定向到资源的位置(URL)上去
  • Content-Base:解析主体中的相对URL时使用的基础URL
  • Content-Encoding:对主体执行的任意编码方式
  • Content-Language:理解主体时最适宜使用的自然语言
  • Content-Length:主体的长度
  • Content-Type:这个主体的对象类型
  • ETag:与此实体相关的实体标记
  • Last-Modified:这个实体最后一次被修改的日期和时间

(7)实体的主体部分:该部分其实就是HTTP要传输的内容,是可选的。HTTP报文可以承载很多类型的数字数据,比如,图片、视频、HTML文档电子邮件、软件应用程序等等。

4、Http 常见的状态码

(1)1xx:请求处理中,请求已被接受,正在处理。

(2)2xx:请求成功,请求被成功处理。

  • 200 :OK,客户端请求成功;
  • 204(请求处理成功,但是没有资源返回)

(3)3xx:重定向,要完成请求必须进一步处理。

  • 301:永久性转移,请求的资源已经被分配到了新的地址
  • 302:暂时重定向
  • 304:已缓存。

(4)4xx:客户端错误,请求不合法。

  • 400:客户端请求报文出现错误,通常是参数错误
  • 401:客户端未认证授权
  • 403:没有权限访问该资源
  • 404:未找到请求的资源
  • 405:不支持该请求方法,如果服务器支持GET,客户端用POST请求就会出现这个错误码

(5)5xx:服务端错误,服务端不能处理合法请求。

  • 500:服务器内部错误。
  • 503:服务不可用,一段时间后可能恢复正常。

5、http/1.1和http/2.0的区别:

(1)多路复用,做到同一个连接并发处理多个请求:HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,并发请求的数量比HTTP1.1大了好几个数量级。

(2)支持首部压缩:HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。

(3)服务器推送:当向支持HTTP2.0的web服务器请求时,服务器会顺便把客户端需要的资源一起推送到客户端,避免客户端再次创建连接发送请求到服务器端获取,这种方式非常合适加载静态资源。

(4)http2.0采用二进制而不是文本格式

6、http 和 https 的区别:

(1)http 和 https 都是基于 TCP 协议,但是 http 是使用明文传输,通讯内容可能被窃听和篡改,客户端也无法验证通讯方的身份,无法保证数据发送到正确的机器上;https 是在 http 的基础上加入了 SSL/TLS,可看成是添加了加密和认证机制的http,使用对称加密、非对称加密、证书等技术进行进行客户端与服务端的数据加密传输,最终达到保证整个通信的安全性。

(2)端口不同:http 使用的是80端口,https 使用的443端口

(3)资源消耗:和 http 通信相比,https通信会由于加解密处理消耗更多的CPU和内存资源

四、应用层其他相关的协议:

(1)DNS域名系统:用于域名解析服务,将域名地址转换为IP地址,基于UDP服务,使用53端口。

DNS底层既使用TCP又使用UDP协议:

① 域名解析时使用UDP协议:客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可,不用经过TCP三次握手,这样DNS服务器负载更低,响应更快。

② 区域传送时使用TCP,主要有一下两点考虑:

  • 辅域名服务器会定时(一般时3小时)向主域名服务器进行查询以便了解数据是否有变动。如有变动,则会执行一次区域传送,进行数据同步。区域传送将使用TCP而不是UDP,因为数据同步传送的数据量比一个请求和应答的数据量要多得多。
  • TCP是一种可靠的连接,保证了数据的准确性。

(2)FTP:定义了文件传输协议,使用21端口。上传下载文件,都要用到FTP服务。

(3)Telnet:远程终端协议,它是一种用于远程登陆的端口,用户可以以自己的身份远程连接到计算机上,提供一种基于DOS模式下的通信服务。

(4)SMTP:定义了简单邮件传送协议,用于发送邮件,使用25号端口。

(5)POP3:与SMTP对应,POP3用于接收邮件。使用110端口。

(6)SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。

(7)TFTP(Trival File Transfer Protocal):简单文件传输协议,该协议在69端口上使用UDP服务。

数据库

1、数据库的常用范式:

  • 第一范式(1NF):指表的列不可再分,数据库中表的每一列都是不可分割的基本数据项,同一列中不能有多个值;
  • 第二范式(2NF):在 1NF 的基础上,还包含两部分的内容:一是表必须有一个主键;二是表中非主键列必须完全依赖于主键,不能只依赖于主键的一部分;
  • 第三范式(3NF):在 2NF 的基础上,消除非主键列对主键的传递依赖,非主键列必须直接依赖于主键。
  • BC范式(BCNF):在 3NF 的基础上,消除主属性对于码部分的传递依赖

2、SQL语句的执行过程:

2.1、客户端的数据库驱动与数据库连接池:

(1)客户端与数据库进行通信前,通过数据库驱动与MySQL建立连接,建立完成之后,就发送SQL语句

(2)为了减少频繁创建和销毁连接造成系统性能的下降,通过数据库连接池维护一定数量的连接线程,当需要进行连接时,就直接从连接池中获取,使用完毕之后,再归还给连接池。常见的数据库连接池有 Druid、C3P0、DBCP

2.2、MySQL架构的Server层的执行过程:

(1)连接器:主要负责跟客户端建立连接、获取权限、维持和管理连接

(2)查询缓存:优先在缓存中进行查询,如果查到了则直接返回,如果缓存中查询不到,在去数据库中查询。

MySQL缓存是默认关闭的,也就是说不推荐使用缓存,并且在MySQL8.0 版本已经将查询缓存的整块功能删掉了。这主要是它的使用场景限制造成的:

  • 先说下缓存中数据存储格式:key(sql语句)- value(数据值),所以如果SQL语句(key)只要存在一点不同之处就会直接进行数据库查询了;
  • 由于表中的数据不是一成不变的,大多数是经常变化的,而当数据库中的数据变化了,那么相应的与此表相关的缓存数据就需要移除掉;

(3)解析器/分析器:分析器的工作主要是对要执行的SQL语句进行词法解析、语法解析,最终得到抽象语法树,然后再使用预处理器对抽象语法树进行语义校验,判断抽象语法树中的表是否存在,如果存在的话,在接着判断select投影列字段是否在表中存在等。

(4)优化器:主要将SQL经过词法解析、语法解析后得到的语法树,通过数据字典和统计信息的内容,再经过一系列运算 ,最终得出一个执行计划,包括选择使用哪个索引

在分析是否走索引查询时,是通过进行动态数据采样统计分析出来;只要是统计分析出来的,那就可能会存在分析错误的情况,所以在SQL执行不走索引时,也要考虑到这方面的因素

(5)执行器:根据一系列的执行计划去调用存储引擎提供的API接口去调用操作数据,完成SQL的执行。

2.3、Innodb存储引擎的执行过程:

  • (1)首先MySQL执行器根据 执行计划 调用存储引擎的API查询数据
  • (2)存储引擎先从缓存池buffer pool中查询数据,如果没有就会去磁盘中查询,如果查询到了就将其放到缓存池中
  • (3)在数据加载到 Buffer Pool 的同时,会将这条数据的原始记录保存到 undo 日志文件中
  • (4)innodb 会在 Buffer Pool 中执行更新操作
  • (5)更新后的数据会记录在 redo log buffer 中
  • (6)提交事务在提交的同时会做以下三件事
  • (7)(第一件事)将redo log buffer中的数据刷入到redo log文件中
  • (8)(第二件事)将本次操作记录写入到 bin log文件中
  • (9)(第三件事)将bin log文件名字和更新内容在 bin log 中的位置记录到redo log中,同时在 redo log 最后添加 commit 标记
  • (10)使用一个后台线程,它会在某个时机将我们Buffer Pool中的更新后的数据刷到 MySQL 数据库中,这样就将内存和数据库的数据保持统一了

3、常用的存储引擎?InnoDB与MyISAM的区别?

存储引擎是对底层物理数据执行实际操作的组件,为Server服务层提供各种操作数据的API。常用的存储引擎有InnoDB、MyISAM、Memory。这里我们主要介绍InnoDB 与 MyISAM 的区别:

(1)事务:MyISAM不支持事务,InnoDB支持事务

(2)锁级别:MyISAM只支持表级锁,InnoDB支持行级锁和表级锁,默认使用行级锁,但是行锁只有通过索引查询数据才会使用,否则将使用表锁。行级锁在每次获取锁和释放锁的操作需要消耗比表锁更多的资源。使用行锁可能会存在死锁的情况,但是表级锁不存在死锁

(3)主键和外键:MyISAM 允许没有任何索引和主键的表存在,不支持外键。InnoDB的主键不能为空且支持主键自增长,如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键,支持外键完整性约束

(4)索引结构:MyISAM 和 InnoDB 都是使用B+树索引,MyISAM的主键索引和辅助索引的Data域都是保存行数据记录的地址。但是InnoDB的主键索引的Data域保存的不是行数据记录的地址,而是保存该行的所有数据内容,而辅助索引的Data域保存的则是主索引的值。

由于InnoDB的辅助索引保存的是主键索引的值,所以使用辅助索引需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这也是为什么不建议使用过长的字段作为主键的原因:由于辅助索引包含主键列,所以,如果主键使用过长的字段,将会导致其他辅助索变得更大,所以争取尽量把主键定义得小一些。

(5)全文索引:MyISAM支持全文索引,InnoDB在5.6版本之前不支持全文索引,5.6版本及之后的版本开始支持全文索引

(6)表的具体行数:

  • ① MyISAM:保存有表的总行数,如果使用 select count() from table 会直接取出出该值,不需要进行全表扫描。
  • ② InnoDB:没有保存表的总行数,如果使用 select count() from table 需要会遍历整个表,消耗相当大。

(7)存储结构:

  • ① MyISAM会在磁盘上存储成三个文件:.frm文件存储表定义,.MYD文件存储数据,.MYI文件存储索引。
  • ② InnoDB:把数据和索引存放在表空间里面,所有的表都保存在同一个数据文件中,InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。

(8)存储空间:

  • ① MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。
  • ② InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。

(9)适用场景:

  • ① 如果需要提供回滚、崩溃恢复能力的ACID事务能力,并要求实现行锁级别并发控制,InnoDB是一个好的选择;
  • ② 如果数据表主要用来查询记录,读操作远远多于写操作且不需要数据库事务的支持,则MyISAM引擎能提供较高的处理效率;

备注:在mysql8.0版本中已经废弃了MyISAM存储引擎

4、事务的ACID与实现原理?

数据库的事务是并发控制的基本单位,是指逻辑上的一组操作,要么全部执行,要么全部不执行。

4.1、事务的ACID:

  • (1)原子性:事务是一个不可分割的工作单元,事务里的操作要么都成功,要么都失败,如果事务执行失败,则需要进行回滚。
  • (2)隔离性:事务的所操作的数据在提交之前,对其他事务的可见程度。
  • (3)持久性:一旦事务提交,它对数据库中数据的改变就是永久的。
  • (4)一致性:事务不能破坏数据的完整性和业务的一致性。例如在转账时,不管事务成功还是失败,双方钱的总额不变。

4.2、ACID的实现原理:

4.2.1、原子性:原子性是通过MySQL的回滚日志undo log实现的:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

4.2.2、隔离性:

(1)事务的隔离级别:

为保证在并发环境下读取数据的完整性和一致性,数据库提供了四种事务隔离级别,隔离级别越高,越能保证数据的完整性和一致性,但对高并发性能影响也越大,执行效率越低。(四种隔离级别从上往下依次升高)

  • 读未提交:允许事务在执行过程中,读取其他事务尚未提交的数据;
  • 读已提交:允许事务在执行过程中读取其他事务已经提交的数据;
  • 可重复读(默认级别):在同一个事务内,任意时刻的查询结果都是一致的;
  • 读序列化:所有事务逐个依次执行,每次读都需要获取表级共享锁,读写会相互阻塞。

(2)事务的并发问题:

如果不考虑事务的隔离性,在事务并发的环境下,可能存在问题有:

  • 更新丢失:两个或多个事务操作相同的数据,然后基于选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题:最后的更新覆盖了其他事务所做的更新。
  • 脏读:指事务A正在访问数据,并且对数据进行了修改(事务未提交),这时,事务B也使用这个数据,后来事务A撤销回滚,并把修改后的数据恢复原值,B读到的数据就与数据库中的数据不一致,即B读到的数据是脏数据。
  • 不可重复读:在一个事务内,多次读取同一个数据,但是由于另一个事务在此期间对这个数据做了修改并提交,导致前后读取到的数据不一致;
  • 幻读:在一个事务中,先后两次进行读取相同的数据(一般是范围查询),但由于另一个事务新增或者删除了数据,导致前后两次结果不一致。

不同的事务隔离级别,在并发环境会存在不同的并发问题:

img

(3)事务隔离性的实现原理:

Innodb事务的隔离级别是由MVVC和锁机制实现的:

① MVCC(Multi-Version Concurrency Control,多版本并发控制)是 MySQL 的 InnoDB 存储引擎实现事务隔离级别的一种具体方式,用于实现读已提交和可重复读这两种隔离级别。而读未提交隔离级别总是读取最新的数据行,无需使用 MVCC。读序列化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

MVCC是通过在每行记录后面保存两个隐藏的列来实现的,一个保存了行的事务ID,一个保存了行的回滚段指针。每开始一个新的事务,都会自动递增产生一个新的事务ID。事务开始时会把该事务ID放到当前事务影响的行事务ID字段中,而回滚段的指针有该行记录上的所有版本数据,在undo log回滚日志中通过链表形式组织,也就是说该值实际指向undo log中该行的历史记录链表。

在并发访问数据库时,对正在事务中的数据做MVCC多版本的管理,以避免写操作阻塞读操作,并且可以通过比较版本解决幻读。

② 锁机制:

MySQL锁机制的基本工作原理就是:事务在修改数据库之前,需要先获得相应的锁,获得锁的事务才可以修改数据;在该事务操作期间,这部分的数据是锁定,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。

  • 排它锁解决脏读
  • 共享锁解决不可重复读
  • 临键锁解决幻读

4.2.3、持久性:

持久性的依靠redo log日志实现在执行SQL时会保存已执行的SQL语句到一个redo log文件,但是为了提高效率,将数据写入到redo log之前,会先写入到内存中的redo log buffer缓存区中。写入过程如下:当向数据库写入数据时,执行过程会首先写入redo log buffer,redo log buffer中修改的数据会定期刷新到磁盘的redo log文件中,这一过程称为刷盘(即redo log buffer写日志到磁盘的redo log file中 )。

redo log buffer的使用可以大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时redo log buffer中修改的数据在内存还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。为了确保事务的持久性,在当事务提交时,会调用fsync接口对redo log进行刷盘 ,刷新频率由 innodb_flush_log_at_trx_commit变量来控制的:

  • 0:表示不刷入磁盘;
  • 1:事务每次提交的时候,就把缓冲池中的数据刷新到磁盘中;
  • 2:提交事务的时候,把缓冲池中的数据写入磁盘文件对应的 os cache 缓存里去,而不是直接进入磁盘文件。可能 1 秒后才会把 os cache 里的数据写入到磁盘文件里去。

4.2.4、一致性:

一致性指的是事务不能破坏数据的完整性和业务的一致性 :

  • 数据的完整性: 实体完整性、列完整性(如字段的类型、大小、长度要符合要求)、外键约束等
  • 业务的一致性:例如在银行转账时,不管事务成功还是失败,双方钱的总额不变。

5、数据库中的锁机制?

当数据库中多个事务并发存取同一数据的时候,若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。MySQL锁机制的基本工作原理就是,事务在修改数据库之前,需要先获得相应的锁,获得锁的事务才可以修改数据;在该事务操作期间,这部分的数据是锁定,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。

按照不同的分类方式,锁的种类可以分为以下几种:

  • 按锁的粒度划分:表级锁、行级锁、页级锁;
  • 按锁的类型划分:共享(锁S锁)、排他锁(X锁);
  • 按锁的使用策略划分:乐观锁、悲观锁;

5.1、表级锁、行级锁、页级锁:

  • 表级锁:最大粒度的锁级别,发生锁冲突的概率最高,并发度最低,但开销小,加锁快,不会出现死锁;
  • 行级锁:最小粒度的所级别,发生锁冲突的概率最小,并发度最高,但开销大,加锁慢,会发生死锁;
  • 页级锁:锁粒度界于表级锁和行级锁之间,对表级锁和行级锁的折中,并发度一般。开销和加锁时间也界于表锁和行锁之间,会出现死锁;

不同的存储引擎支持不同的锁机制:

  • InnoDB存储引擎支持行级锁和表级锁,默认情况下使用行级锁,但只有通过索引进行查询数据,才使用行级锁,否就使用表级锁。
  • MyISAM和MEMORY存储引擎采用的是表级锁;
  • BDB存储引擎使用的是页面锁,但也支持表级锁;

5.2、InnoDB的行锁:

InnoDB的行锁有两种类型:

  • 共享锁(S锁、读锁):多个事务可以对同一数据行共享一把S锁,但只能进行读不能修改;
  • 排它锁(X锁、写锁):一个事务获取排它锁之后,可以对锁定范围内的数据行执行写操作,在锁定期间,其他事务不能再获取这部分数据行的锁(共享锁、排它锁),只允许获取到排它锁的事务进行更新数据。

对于update,delete,insert 操作,InnoDB会自动给涉及的数据行加排他锁;对于普通SELECT语句,InnoDB不会加任何锁。

5.3、InnoDB的表锁与意向锁:

因为InnoDB引擎允许行锁和表锁共存,实现多粒度的锁机制,但是表锁和行锁虽然锁定范围不同,但是会相互冲突。当你要加表锁时,势必要先遍历该表的所有记录,判断是否有排他锁。这种遍历检查的方式显然是一种低效的方式,MySQL引入了意向锁,来检测表锁和行锁的冲突。

意向锁也是表级锁,分为读意向锁(IS锁)和写意向锁(IX锁)。当事务要在记录上加上行锁时,则先在表上加上对应的意向锁。之后事务如果想进行锁表,只要先判断是否有意向锁存在,存在时则可快速返回该表不能启用表锁,否则就需要等待,提高效率。

5.4、InnoDB行锁的实现与临键锁:

InnoDB的行锁是通过给索引上的索引项加锁来实现的。只有通过索引检索数据,才能使用行锁,否则将使用表锁。

在InnoDB中,为了解决幻读的现象,引入了临键锁(next-key)。根据索引,划分为一个个左开右闭的区间。当进行范围查询的时候,若命中索引且能够检索到数据,则锁住记录所在的区间和它的下一个区间。其实,临键锁(Next-Key) = 记录锁(Record Locks) + 间隙锁(Gap Locks)

  • 间隙锁:当使用范围查询而不是精准查询进行检索数据,并请求共享或排它锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做间隙(GAP)。
  • 记录锁:当使用唯一索引,且记录存在的精准查询时,使用记录锁

5.5、利用锁机制解决并发问题:

  • X锁解决脏读
  • S锁解决不可重复读
  • 临键锁解决幻读

InnoDB存储引擎锁机制的详细内容和MyISAM存储引擎的锁机制的详细内容可以阅读这篇文章:https://blog.csdn.net/a745233700/article/details/84504209

6、MySQL索引的实现原理:

索引本质上就是一种通过减少查询需要遍历行数,加快查询性能的数据结构,避免数据库进行全表扫描,好比书的目录,让你更快的找到内容。(一个表最多16个索引)

6.1、索引的优缺点:

(1)索引的优点:

  • 减少查询需要检索的行数,加快查询速度,避免进行全表扫描,这也是创建索引的最主要的原因。
  • 如果索引的数据结构是B+树,在使用分组和排序时,可以显著减少查询中分组和排序的时间。
  • 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。

(2)索引的缺点:

  • 当对表中的数据进行增加、删除和修改时,索引也要进行更新,维护的耗时随着数据量的增加而增加。
  • 索引需要占用物理空间,如果要建立聚簇索引,那么需要的空间就会更大。

6.2、索引的使用场景:

(1)在哪些列上面创建索引:

  • WHERE子句中经常出现的列上面创建索引,加快条件的判断速度。
  • 按范围存取的列或者在group by或order by中使用的列,因为索引已经排序,这样可以利用索引加快排序查询时间。
  • 经常用于连接的列上,这些列主要是一些外键,可以加快连接的速度;
  • 作为主键的列上,强制该列的唯一性和组织表中数据的排列结构;

(2)不在哪些列建索引?

  • 区分度不高的列。由于这些列的取值很少,例如性别,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。
  • 在查询中很少的列不应该创建索引。由于这些列很少使用到,但增加了索引,反而降低了系统的维护速度和增大了空间需求。
  • 当添加索引造成修改成本的提高 远远大于 检索性能的提高时,不应该创建索引。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。
  • 定义为text, image和bit数据类型的列不应该增加索引。这些列的数据量要么相当大,要么取值很少。

6.3、 索引的分类:

(1)普通索引、唯一索引、主键索引、全文索引、组合索引。

  • 普通索引:最基本的索引,没有任何限制
  • 唯一索引:但索引列的值必须唯一,允许有空值,可以有多个NULL值。如果是组合索引,则列值的组合必须唯一。
  • 主键索引:一种特殊的唯一索引,不允许有空值。
  • 全文索引:全文索引仅可用于 MyISAM 表,并只支持从CHAR、VARCHAR或TEXT类型,用于替代效率较低的like 模糊匹配操作,而且可以通过多字段组合的全文索引一次性全模糊匹配多个字段。
  • 组合索引:主要是为了提高mysql效率,创建组合索引时应该将最常用作限制条件的列放在最左边,依次递减。

(2)聚簇索引与非聚簇索引:

如果按数据存储的物理顺序与索引值的顺序分类,可以将索引分为聚簇索引与非聚簇索引两类:

  • 聚簇索引:表中数据存储的物理顺序与索引值的顺序一致,一个基本表最多只能有一个聚簇索引,更新聚簇索引列上的数据时,往往导致表中记录的物理顺序的变更,代价较大,因此对于经常更新的列不宜建立聚簇索引
  • 非聚簇索引:表中数据的物理顺序与索引值的顺序不一致的索引组织,一个基本表可以有多个聚簇索引。

6.4、索引的数据结构:

常见的索引的数据结构有:B+Tree、Hash索引。

(1)Hash索引:MySQL中只有Memory存储引擎支持hash索引,是Memory表的默认索引类型。hash索引把数据以hash值形式组织起来,因此查询效率非常高,可以一次定位。

hash索引的缺点:

  • Hash索引仅能满足等值的查询,不能满足范围查询、排序。因为数据在经过Hash算法后,其大小关系就可能发生变化。
  • 当创建组合索引时,不能只使用组合索引的部分列进行查询。因为hash索引是把多个列数据合并后再计算Hash值,所以对单独列数据计算Hash值是没有意义的。
  • 当发生Hash碰撞时,Hash索引不能避免表数据的扫描。因为仅仅比较Hash值是不够的,需要比较实际的值以判定是否符合要求。

(2)B+Tree索引:B+Tree是mysql使用最频繁的一个索引数据结构,是Innodb和Myisam存储引擎模式的索引类型。B+Tree索引在查找时需要从根节点到叶节点进行多次IO操作,在查询速度比不上Hash索引,但是更适合排序等操作。

B+Tree索引的优点:

  • 页内节点不存储内容,每次IO可以读取更多的行,大大减少磁盘I/O读取次数
  • 带顺序访问指针的B+Tree:B+Tree所有索引数据都存储在叶子结点上,并且增加了顺序访问指针,每个叶子节点都有指向相邻叶子节点的指针,这样做是为了提高区间查询效率。

6.5、为什么使用B+Tree作为索引:

索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,磁盘I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的数据结构要尽量减少查找过程中磁盘I/O的存取次数。

(1)局部性原理与程序预读:

由于磁盘本身存取就比主存慢很多,再加上机械运动耗费,因此为了提高效率,要尽量减少磁盘I/O。为了达到这个目的,磁盘往往不是严格按需读取,而是每次都会预读,即使只需要一个字节,磁盘也会从这个位置开始,顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。程序运行期间所需要的数据通常比较集中。

由于磁盘顺序读取的效率很高(不需要寻道时间,只需很少的旋转时间),因此对于具有局部性的程序来说,预读可以提高I/O效率。预读的长度一般为页的整倍数。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

(2)B+Tree索引的性能分析:

上文说过一般使用磁盘I/O次数评价索引结构的优劣。我们先从B树分析,B树检索一次最多需要访问h个节点,同时,数据库巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,即每次新建节点时,直接申请一个页的空间,这样就保证一个节点在物理上也存储在一个页里,加之计算机存储分配都是按页对齐的,这样就实现了每个节点只需要一次I/O就可以完全载入。B树中一次检索最多需要h-1次I/O(根节点常驻内存),时间复杂度为O(h)=O(logdN)。一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小。综上所述,用B树作为索引结构效率是非常高的。

而红黑树这种结构,虽然时间复杂度也为O(h),但是h明显要深的多,并且由于逻辑上很近的节点,在物理上可能很远,无法利用局部性,所以IO效率明显比B树差很多。

另外,B+Tree更适合作为索引的数据结构,原因和内节点出度d有关。从上面分析可以看到,d越大索引的性能越好,而出度d的上限取决于节点内key和data的大小,由于B+Tree内节点去掉了data域,因此可以拥有更大的出度,磁盘IO的次数也就更少了。

(3)B+树索引 和 B树索引 的对比?

根据B-Tree 和 B+Tree的结构,我们可以发现B+树相比于B树,在文件系统或者数据库系统当中,更有优势,原因如下:

  • (1)B+树有利于对数据库的扫描:B树在提高了磁盘IO性能的同时并没有解决元素遍历的效率低下的问题,而B+树只需要遍历叶子节点就可以解决对全部关键字信息的扫描,所以范围查询、排序等操作,B+树有着更高的性能。
  • (2)B+树的磁盘IO代价更低:B+树的内部结点的data域并没有存储数据,因此其内部结点相对于B树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多,相对来说I/O读写次数也就降低了。
  • (3)B+树的查询效率更加稳定:由于B+树的内部结点只是叶子结点中关键字的索引,并不存储数据。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

(4)MySQL的 InnoDB 和 MyISAM 存储引擎中B+Tree索引的实现?

MyISAM和InnoDB都是使用B+树索引,MyISAM的主键索引和辅助索引的Data域都是保存行的地址,但是InnoDB的主键索引保存的不是行的地址,而是保存该行的所有所有数据,而辅助索引的Data域保存的则是主索引的值。

索引的长度限制:

  • 对于 Innodb 的组合索引,如果各个列中的长度超过767字节的,则会对超过767字节的列取前缀索引;对于 Innodb 的单列索引,如果列的长度超过767的,则取前缀索引(取前255字符)
  • 对于 MyISAM 的组合索引,所创建的索引长度和不能超过1000 bytes,否则会报错,创建失败;对于 MyISAM 的单列索引,最大长度也不能超过1000,否则会报警,但是创建成功,最终创建的是前缀索引(取前333个字符)

7、SQL优化和索引优化、表结构优化:

(1)MySQL的SQL优化和索引优化:

一、索引优化:

1、like语句的前导模糊查询不使用索引:

select * from doc where title like '%XX'; --不能使用索引
select * from doc where title like 'XX%'; --非前导模糊查询,可以使用索引

2、负向条件查询不能使用索引:

负向条件有:!=、<>、not in、not exists、not like 等

例如下面SQL语句:(假设status的取值为0、1、2、3、4)

select * from doc where status != 1 and status != 2; --不能使用索引

select * from doc where status in (0,3,4); --优化为 in 查询,可以使用索引

3、范围条件右边的列不能使用索引(范围列可以用到索引):

范围条件有:<、<=、>、>=、between等。

索引最多用于一个范围列,如果查询条件中有两个范围列则无法全用到索引。

假如有联合索引 (emp_no 、title、from_date ),那么下面的 SQL 中 emp_no 可以用到索引,而title 和 from_date 则使用不到索引。

select * from employees.titles where emp_no < 10010' and title='Senior Engineer'and from_date between '1986-01-01' and '1986-12-31'

4、在索引列做任何操作(计算、函数、表达式)会导致索引失效而转向全表扫描:

  • select * from doc where YEAR(create_time) <= '2016'; -- 不能使用索引
  • select * from doc where create_time<= '2016-01-01'; -- 可以使用索引
  • select * from order where date < = CURDATE(); -- 不能使用索引
  • select * from order where date < = '2018-01-2412:00:00'; -- 可以使用索引
  • select id from t where substring(name,1,3)=’abc’ -- 不能使用索引
  • select id from t where name like ‘abc%’ -- 可以使用索引
  • select id from t where num/2=100 -- 不能使用索引
  • select id from t where num=100*2 -- 可以使用索引

5、where 子句中索引列使用参数,也会导致索引失效:

因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num -- 不能使用索引

select id from t with(index(索引名)) where num=@num --可以改为强制查询使用索引:

6、强制类型转换会导致全表扫描:

字符串类型不加单引号会导致索引失效,因为mysql会自己做类型转换,相当于在索引列上进行了操作。

如果 phone 字段是 varchar 类型,则下面的 SQL 不能命中索引,因为内部发生的类型转换。

select * from user where phone=13800001234; -- 不能使用索引

select * from user where phone='13800001234'; -- 可以使用索引

7、is null, is not null 在无法使用索引,不过在mysql的**高版本已经做了优化,允许使用索引**

select id from t where num is null; -- mysql低版本不能使用索引

select id from t where num=0; -- 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询

\8、使用组合索引时,要符合最左前缀原则:**

组合索引的字段数不允许超过5个。如果在a,b,c三个字段上建立联合索引 index(a,b,c),那么他会自动建立 a、(a,b)、(a,b,c) 三组索引。

(1)建立联合索引的时候,区分度最高的字段在最左边:

(2)存在等号和非等号混合判断条件时,在建立索引时,把等号条件的列前置,如 where a > ? and b= ?,那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。

(3)最左前缀查询时,并不是指SQL语句的where顺序要和联合索引一致,但还是建议 where 条件的顺序和联合索引一致。

(4)假如index(a,b,c), where a=3 and b like 'abc%' and c=4,a能用,b能用,c不能用。

9、利用覆盖索引来进行查询操作,避免回表,减少select * 的使用 :

覆盖索引:被查询列要被所建的索引覆盖,被查询列的数据能从索引中直接取得,不用通过行定位符 再到 row 上获取,加速查询速度。

例如登录业务需求,SQL语句如下。

Select uid, login_time from user where login_name=? and passwd=?

可以建立(login_name, passwd, login_time)的联合索引,由于 login_time 已经建立在索引中了,被查询的 uid 和 login_time 就不用去 row 上获取数据了,从而加速查询。

10、利用索引下推减少回表的次数:

索引下推是Mysql5.6版本推出的功能,用于优化查询。

在不使用索引下推的情况下,在使用非主键索引进行查询时,存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件 。

在使用索引下推的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器。

索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。

11、使用前缀索引:

短索引不仅可以提高查询性能而且可以节省磁盘空间和I/O操作,减少索引文件的维护开销,但缺点是不能用于 ORDER BY 和 GROUP BY 操作,也不能用于覆盖索引。比如有一个varchar(255)的列,如果该列在前10个或20个字符内,可以做到既使前缀索引的区分度接近全列索引,那么就不要对整个列进行索引。为了减少key_len,可以考虑创建前缀索引,即指定一个前缀长度,可以使用count(distinct leftIndex(列名, 索引长度))/count(*) 来计算前缀索引的区分度(计算前缀索引的区分度在文章第三部分会介绍)。

12、order by、group by后面的列如果有索引,可以利用索引的有序性可以消除排序带来的CPU开销。

(1)order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort 的情况,影响查询性能。例如对于语句 where a= ? and b= ? order by c,可以建立联合索引(a,b,c)。

(2)如果索引中有范围查找,那么索引有序性无法利用,如 WHERE a > 10 ORDER BY b; 索引(a,b)无法排序。

(3)如果是前缀索引,是不能消除排序的

(4)order by排序字段顺序,即asc/desc升降要跟索引保持一致,充分利用索引的有序性来消除排序带来的CPU开销

12、进行join联表查询的字段需要建立索引,join最好不要超过三个表,需要 join 的字段,数据类型必须一致:

多表关联查询时,保证被关联的字段需要有索引。left join是由左边决定的,左边的数据一定都有,所以右边是我们的关键点,建立索引要建右边的。当然如果索引在左边,可以用right join。

13、单表索引建议控制在5个以内。

索引不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,同时也会暂用空间。一个表的索引数较好不要超过5个。

14、SQL 性能优化 explain 中的 type:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。

consts:单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。

ref:使用普通的索引

range:对索引进行范围检索。

当 type=index 时,索引物理文件全扫,速度非常慢。

15、业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引,防止脏数据产生:

不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的。另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然。

16、更新十分频繁、数据区分度不高的列不宜建立索引:

数据更新会变更 B+ 树,在更新频繁的字段建立索引会大大降低数据库性能。类似于“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似。一般区分度在80%以上的时候就可以建立索引,区分度可以使用 count(distinct(列名))/count(*) 来计算。

二、SQL语句优化:

1、减少请求的数据量:

(1)只返回必要的列,用具体的字段列表代替 select * 语句

MySQL数据库是按照行的方式存储,而数据存取操作都是以一个页大小进行IO操作的,每个IO单元中存储了多行,每行都是存储了该行的所有字段。所以无论取一个字段还是多个字段,实际上数据库在表中需要访问的数据量其实是一样的。但是如果查询的字段都在索引中,也就是覆盖索引,那么可以直接从索引中获取对应的内容直接返回,不需要进行回表,减少IO操作。除此之外,当存在 order by 操作的时候,select 子句中的字段多少会在很大程度上影响到我们的排序效率。

(2)只返回必要的行,使用 Limit 语句来限制返回的数据。如果不使用 Limit 的话,MySQL将会一行一行的将全部结果按照顺序查找,最后返回结果,借助 Limit 可以实现当找到指定行数时,直接返回查询结果,提高效率

\2、优化深度分页的场景:******利用延迟关联或者子查询****

对于 limit m, n 的分页查询,越往后面翻页(即m越大的情况下)SQL的耗时会越来越长,对于这种应该先取出主键id,然后通过主键id跟原表进行Join关联查询。因为MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。

延迟关联示例如下,先快速定位需要获取的 id 段,然后再关联:

# 延迟关联:通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据

# 覆盖索引:select的数据列只用从索引中就能够得到,不用回表查询

select a.* from 表1 a,(select id from 表1 where 条件 limit 100000,20) b where a.id=b.id;

但对于深度分页的情况,最好还是将上次遍历到的最末尾的数据ID传给数据库,然后直接定位到该ID处 再 往后面遍历数据

3、分解大连接查询:

将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:

  • (1)减少锁竞争;
  • (2)让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
  • (3)分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
  • (4)在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。
  • (5)查询本身效率也可能会有所提升。比如使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。

4、避免使用select的内联子查询:

在select后面有子查询的情况称为内联子查询,SQL返回多少行,子查询就需要执行过多少次,严重影响SQL性能。

5、尽量使用Join代替子查询:

由于MySQL的优化器对于子查询的处理能力比较弱,所以不建议使用子查询,可以改写成Inner Join,之所以 join 连接效率更高,是因为 MySQL不需要在内存中创建临时表

select 



    b.member_id,b.member_type, a.create_time,a.device_model 



from 



    member_operation_log a 



inner join 



    (select member_id,member_type from member_base_info where `status` = 1) as b 



on 



    a.member_id = b.member_id;

6、多张大数据量的表进行JOIN连接查询,最好先过滤在JOIN:

在多个表进行 join 连接查询的时候,最好先在一个表上先过滤好数据,然后再用过滤好的结果集与另外的表 Join,这样可以尽可能多的减少不必要的 IO 操作,大大节省 IO 操作所消耗的时间

7、避免在使用or来连接查询条件:

如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描。

8、union、in、or 都能够命中索引,但推荐使用 in:

(1)union:能够命中索引,并且MySQL 耗费的 CPU 最少

select * from doc where status=1
union all
select * from doc where status=2;

(2)in:能够命中索引,查询优化耗费的 CPU 比 union all 多,但可以忽略不计

select * from doc where status in (1, 2);

(3)or:新版的 MySQL 能够命中索引,但是如果一个字段有建立索引、一个字段没有建立索引,那么将导致索引失效而进行全表扫描,or 查询优化耗费的 CPU 比 in 多

select * from doc where status = 1 or status = 2

对于上面三种关键词:union all 分两步执行,而 in 和 or 只用了一步,效率高一点。用 or 的执行时间比 in 时间长。因为使用 or 条件查询,会先判断一个条件进行筛选,再判断 or 中另外的条件再筛选,而 in 查询直接一次在 in 的集合里筛选,并且or 查询优化耗费的 CPU 比 in 多,所以推荐使用in

9、对于连续的数值,能用 between 就不要用 in:

10、小表驱动大表,即小的数据集驱动大的数据集:

in 和 exists 都可以用于子查询,那么 MySQL 中 in 和 exists 有什么区别呢?

  • (1)使用exists时会先进行外表查询,将查询到的每行数据带入到内表查询中看是否满足条件;使用in一般会先进行内表查询获取结果集,然后对外表查询匹配结果集,返回数据。
  • (2)in在内表查询或者外表查询过程中都会用到索引;exists仅在内表查询时会用到索引
  • (3)一般来说,当子查询的结果集比较大,外表较小使用exist效率更高;当子查询的结果集较小,外表较大时,使用in效率更高。
  • (4)对于 not in 和 not exists,not exists 效率比 not in 的效率高,与子查询的结果集无关,因为 not in 对于内外表都进行了全表扫描,没有使用到索引。not exists的子查询中可以用到表上的索引。

11、使用union all 替换 union:

当SQL语句需要union两个查询结果集合时,这两个结果集合会以union all的方式被合并,然后再输出最终结果前进行排序。如果用union all替代union,这样排序就不是不要了,效率就会因此得到提高.。需要注意的是,UNION ALL 将重复输出两个结果集合中相同记录。

12、优化Group by,使用where子句替换Having子句:

避免使用having子句,having只会在检索出所有记录之后才会对结果集进行过滤,这个处理需要排序分组,如果能通过where子句提前过滤查询的数目,就可以减少这方面的开销。

on、where、having这三个都可以加条件的子句,on是最先执行,where次之,having最后。

提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉。

低效: SELECT JOB, AVG(SAL) FROM EMP GROUP by JOB HAVING JOB = ‘PRESIDENT' OR JOB = ‘MANAGER'

高效: SELECT JOB, AVG(SAL) FROM EMP WHERE JOB = ‘PRESIDENT' OR JOB = ‘MANAGER' GROUP by JOB

13、尽量使用数字型字段:

若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能。引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

14、写出统一的SQL语句:

对于以下两句SQL语句,很多人都认为是相同的。不过数据库查询优化器则认为是不同的,虽然只是大小写不同,但必须进行两次解析,生成2个执行计划。所以应该保证相同的查询语句在任何地方都一致,多一个空格都不行。

  • select * from dual
  • select * From dual

15、使用复合索引须遵守最左前缀原则:

复合索引必须使用到最左边字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。

16、当需要全表删除且无需回滚时,使用Truncate替代delete:

1、删除速度:drop>truncate>delete;

2、删除方式:

(1)执行delete操作时,每次从表中删除一行,并且同时将该行的的删除操作记录在日志文件中。delete可根据where条件删除表中指定的数据,如果不指定where子句,则跟truncate一样,可以删除表中所有记录。
(2)truncate通过释放存储表数据所用的页来删除数据,一次性删除表中所有记录,不会将删除的数据记录在日志文件中,只在日志文件中记录页的释放,使用的系统和事务日志资源少,所以执行速度比delete快。对于外键约束引用的表,不能使用 truncate table,而应使用不带 where 子句的 delete 语句。
(3)drop则删除整个表的结构和数据,但是被依赖的约束、触发器、索引、依赖于该表的存储过程、函数将被保留,但其状态会变为invalid
3、删除后,表和索引所占空间:

(1)delete操作不会减少表或索引所占用的空间;
(2)当表被truncate后,这个表和索引所占用的空间会恢复到初始大小,但表结构及其列、约束、索引的定义等保持不变。
(3)drop语句将表所占用的空间全释放掉。
4、提交方式:delete是DML,需要手动提交操作才能生效,可以回滚,可以触发触发器;truncate和drop是DDL,会隐式提交,不能回滚,不会触发触发器。

5、使用场景:

(1)如果想删除表,当然用drop;
(2)如果想保留表而将所有数据删除,而且和事务无关,用truncate即可;
(3)如果和事务有关,或者想触发trigger,还是用delete;

17、使用表的别名:

当在SQL语句中连接多个表时, 使用表的别名并把别名前缀用于每个Column上,这样可以减少解析的时间并减少那些由Column歧义引起的语法错误。

18、避免使用耗费资源的操作:

带有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL语句,会启动SQL引擎执行耗费资源的排序功能,DISTINCT需要一次排序操作,而其他的至少需要执行两次排序。通常。带有UNION, MINUS , INTERSECT的SQL语句都可以用其他方式重写,如果你的数据库的SORT_AREA_SIZE调配得好, 使用UNION , MINUS, INTERSECT也是可以考虑的, 毕竟它们的可读性很强。

19、Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志。

20、应尽可能的避免更新聚簇索引数据列,因为聚簇索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。

21、尽量使用表变量来代替临时表。

22、考虑使用“临时表”暂存中间结果。临时表并不是不可使用,适当地使用它们可以使某些查询更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。将临时结果暂存在临时表,后面的查询就在临时表中查询了,这可以避免程序中多次扫描主表,也大大减少了程序执行***享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。但是,对于一次性事件,较好使用导出表。

23、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

24、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

25、避免频繁创建和删除临时表,以减少系统表资源的消耗。

26、尽量避免使用游标,因为游标的效率较差。与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。

27、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。

28、尽量避免大事务操作,提高系统并发能力。

29、在运行代码中,尽量使用PreparedStatement来查询,不要用Statement。

三、索引的选择性与前缀索引:

既然索引可以加快查询速度,那么是不是只要是查询语句需要,就建上索引?答案是否定的。因为索引虽然加快了查询速度,但索引也是有代价的:索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。一般两种情况下不建议建索引。

第一种情况是表记录比较少,没必要建索引,让查询做全表扫描就好了。

第二种情况是索引的选择性较低。所谓索引的选择性,是指 不重复的索引值 与 表记录数量 的比值:

显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。

例如,employees.titles表,如果title字段经常被单独查询,是否需要建索引,我们看一下它的选择性:

SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;



+-------------+



| Selectivity |



+-------------+



|      0.0000 |



+-------------+

title的选择性不足0.0001(精确值为0.00001579),所以实在没有什么必要为其单独建索引。

有一种与索引选择性有关的索引优化策略叫做前缀索引,就是用列的前缀代替整个列作为索引key,当前缀长度合适时,可以做到既使得前缀索引的选择性接近全列索引,同时因为索引key变短而减少了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。

假设employees表只有一个索引<emp_no>,那么如果我们想按名字搜索一个人,就只能全表扫描了:</emp_no>

EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';



+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+



| id | select_type | table     | type | possible_keys | key  | key_len | ref  | rows   | Extra       |



+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+



|  1 | SIMPLE      | employees | ALL  | NULL          | NULL | NULL    | NULL | 300024 | Using where |



+----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+

如果频繁按名字搜索员工,这样显然效率很低,因此我们可以考虑建索引。有两种选择,建<first_name>或<first_name, last_name>,看下两个索引的选择性:</first_name>

SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;



+-------------+



| Selectivity |



+-------------+



|      0.0042 |



+-------------+



SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;



+-------------+



| Selectivity |



+-------------+



|      0.9313 |



+-------------+

<first_name>显然选择性太低,<first_name, last_name>选择性很好,但是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?可以考虑用first_name和last_name的前几个字符建立索引,例如<first_name, left(last_name, 3)>,看看其选择性:</first_name>

SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;



+-------------+



| Selectivity |



+-------------+



|      0.7879 |



+-------------+

选择性还不错,但离0.9313还是有点距离,那么把last_name前缀加到4:

SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;



+-------------+



| Selectivity |



+-------------+



|      0.9007 |



+-------------+

这时选择性已经很理想了,而这个索引的长度只有18,比<first_name, last_name>短了接近一半,我们把这个前缀索引建上:

ALTER TABLE employees.employees



ADD INDEX `first_name_last_name4` (first_name, last_name(4));

此时再执行一遍按名字查询,比较分析一下与建索引前的结果:

SHOW PROFILES;



+----------+------------+---------------------------------------------------------------------------------+



| Query_ID | Duration   | Query                                                                           |



+----------+------------+---------------------------------------------------------------------------------+



|       87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |



|       90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |



+----------+------------+---------------------------------------------------------------------------------+

性能的提升是显著的,查询速度提高了120多倍。

前缀索引兼顾索引大小和查询速度,但是其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于覆盖索引

(2)MySQL的表结构优化:

数据库操作中最为耗时的操作就是 IO 处理,大部分数据库操作 90% 以上的时间都花在了 IO 读写上面。所以减少 IO 次数可以在很大程度上提高数据库操作的性能。

由于MySQL数据库是基于行存储的数据库,而数据库IO操作的时候是以 page 的方式,也就是说,如果我们每行记录所占用的空间量减小,就会使每个 page 中可存放的数据行数增大,那么每次 IO 可访问的行数也就增多了。反过来说,处理相同行数的数据,需要访问的 page 就会减少,也就是 IO 操作次数降低,直接提升性能。此外,由于我们的内存是有限的,增加每个page中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存换中数据命中的几率,也就是缓存命中率。我们无法改变数据库中需要存储的数据,但是我们可以在数据的存储方式方面做一些优化。

MySQL的表结构优化:

四、表结构优化之数据类型的选择:

下面关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景。但是精细化的数据类型可能带来维护成本的提高,过度优化也可能会带来其他的问题。

1、数字类型:

(1)非万不得已不要使用DOUBLE,不仅仅只是存储长度的问题,同时还会存在精确性的问题。

(2)固定精度的小数,也不建议使用DECIMAL,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。

(3)对于整数的存储,在数据量较大的情况下,建议区分开 TINYINT / INT / BIGINT 的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。

(4)int类型只增主键字段=>4字节=>每个字节8位=>32位,在CPU加载一条指令的时候,4字节是和CPU寄存器的运算有关,如:64位,由于之前的系统一般都是32位的,所以在运算4字节的数据是刚好的,效率最高,而现今我们系统基本都是64位的时候,其实没有更好的利用好CPU运算,所以在设计表字段建议,使用8字节的主键bigint,而不是直接使用int来做主键。

2、字符类型:

(1)非万不得已不要使用 TEXT 数据类型,其处理方式决定了他的性能要低于char或者是varchar类型的处理。

(2)对于定长字段,建议使用 CHAR 类型,不定长字段尽量使用 VARCHAR,且设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。

char(n) 不管该字段是否存储数据,都占n个字符的存储空间;varchar 不存的时候不占空间,存多长数据就占多少空间,可以节省存储空间。

3、时间类型:

(1)尽量使用TIMESTAMP类型,因为其存储空间只需要 DATETIME 类型的一半。但是timestamp存储的数据所以被限制在了1970~2038年之内。

(2)对于只需要精确到某一天的数据类型,建议使用DATE类型,因为他的存储空间只需要3个字节,比TIMESTAMP还少。

4、ENUM & SET:

对于状态字段,可以尝试使用 ENUM 来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用SET类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。

5、字符编码:

字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。

(1)纯拉丁字符能表示的内容,没必要选择 latin1 之外的其他字符编码,因为这会节省大量的存储空间;

(2)如果我们可以确定不需要存放多种语言,就没必要非得使用UTF8或者其他UNICODE字符类型,这回造成大量的存储空间浪费;

(3)MySQL的数据类型可以精确到字段,所以当我们需要大型数据库中存放多字节数据的时候,可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量,进而降低 IO 操作次数并提高缓存命中率。

6、LOB类型:

强烈反对在数据库中存放 LOB 类型数据,虽然数据库提供了这样的功能,但这不是他所擅长的,我们更应该让合适的工具做他擅长的事情,才能将其发挥到极致。

五、表结构设计:

上面几点的优化都是为了减少每条记录的存储空间大小,让每个数据库中能够存储更多的记录条数,以达到减少 IO 操作次数,提高缓存命中率。下面这个优化建议可能很多开发人员都会觉得不太理解,因为这是典型的反范式设计,而且也和上面的几点优化建议的目标相违背。

1、适当拆分:

我们可能希望将一个完整对象对应一张数据库表,这对于应用程序开发来说是很友好的,但有时可能会在性能上带来较大的问题。当我们的表中存在类似于 TEXT 或者是很大的 varchar 类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们可以将其拆分到另外的独立表中,以减少常用数据表所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。

2、适度冗余:

冗余确实这样做会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做,比如:被频繁引用且只能通过 Join连接 2张(或者以上)大表的方式才能得到的独立小字段,这样的场景由于每次Join连接仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新。

3、尽量使用 not null:

(1)null 类型比较特殊,SQL 难优化。虽然 MySQL null 类型和 Oracle 的 null 有差异,会进入索引中,但如果是一个组合索引,那么这个 null 类型的字段会极大影响整个索引的效率。

(2)很多人觉得 null 会节省一些空间,所以尽量让 null 来达到节省IO的目的,但是大部分时候这会适得其反,因为对于允许为 null 的字段,mysql 会多需要一个1字节记录是否为 null;同时也带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。所以尽量确保 DEFAULT 值不是 NULL,数字可以默认0,字符串默认“”。

8、数据库参数优化:

MySQL属于 IO 密集型的应用程序,主要职责就是数据的管理及存储工作。而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级。所以,要优化数据库,首先第一步需要优化的就是 IO,尽可能将磁盘IO转化为内存IO。所以对于MySQL数据库的参数优化上,主要针对减少磁盘IO的参数做优化:比如使用 query_cache_size 调整查询缓存的大小,使用 innodb_buffer_pool_size 调整缓冲区的大小;

9、explain的执行计划:

执行计划是SQL语句经过查询分析器后得到的 抽象语法树 和 相关表的统计信息 作出的一个查询方案,这个方案是由查询优化器自动分析产生的。由于是动态数据采样统计分析出来的结果,所以可能会存在分析错误的情况,也就是存在执行计划并不是最优的情况。通过explain关键字知道MySQL是如何执行SQL查询语句的,分析select 语句的性能瓶颈,从而改进我们的查询,explain的结果如下:

img

重要的有id、type、key、key_len、rows、extra:

(1)id:id列可以理解为SQL执行顺序的标识,有几个select 就有几个id。

  • id值不同:id值越大优先级越高,越先被执行;
  • id值相同:从上往下依次执行;
  • id列为null:表示这是一个结果集,不需要使用它来进行查询。

(2)select_type:查询的类型,主要用于区分普通查询、联合查询、子查询等复杂的查询;

(3)table:表示 explain 的一行正在访问哪个表

(4)type:访问类型,即MySQL决定如何查找表中的行。依次从好到差:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL,除了all之外,其他的 type 类型都可以使用到索引,除了 index_merge 之外,其他的type只可以用到一个索引。一般要求type为 ref 级别,范围查找需要达到 range 级别。

  • system:表中只有一条数据匹配(等于系统表),可以看成 const 类型的特例
  • const:通过索引一次就找到了,表示使用主键索引或者唯一索引
  • eq_ref:主键或者唯一索引中的字段被用于连接使用,只会返回一行匹配的数据
  • ref:普通索引扫描,可能返回多个符合查询条件的行。
  • fulltext:全文索引检索,全文索引的优先级很高,若全文索引和普通索引同时存在时,mysql不管代价,优先选择使用全文索引。
  • ref_or_null:与ref方法类似,只是增加了null值的比较。
  • index_merge:表示查询使用了两个以上的索引,索引合并的优化方法,最后取交集或者并集,常见and ,or的条件使用了不同的索引。
  • unique_subquery:用于where中的in形式子查询,子查询返回不重复值唯一值;
  • index_subquery:用于in形式子查询使用到了辅助索引或者in常数列表,子查询可能返回重复值,可以使用索引将子查询去重。
  • range:索引范围扫描,常见于使用>,<,between ,in ,like等运算符的查询中。
  • index:索引全表扫描,把索引树从头到尾扫描一遍;
  • all:遍历全表以找到匹配的行(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)
  • NULL:MySQL在优化过程中分解语句,执行时甚至不用访问表或索引

(5)possible_keys:查询时可能使用到的索引

(6)key:实际使用哪个索引来优化对该表的访问

(7)key_len:实际上用于优化查询的索引长度,即索引中使用的字节数。通过这个值,可以计算出一个多列索引里实际使用了索引的哪写字段。

(8)ref:显示哪个字段或者常量与key一起被使用

(9)rows:根据表统计信息及索引选用情况,大致估算此处查询需要读取的行数,不是精确值。

(10)extra:其他的一些额外信息

  • using index:使用覆盖索引
  • using index condition:查询的列未被索引覆盖,where筛选条件使用了索引
  • using temporary:用临时表保存中间结果,常用于 group by 和 order by 操作中,通常是因为 group by 的列上没有索引,也有可能是因为同时有group by和order by,但group by和order by的列又不一样,一般看到它说明查询需要优化了
  • using filesort:MySQL有两种方式对查询结果进行排序,一种是使用索引,另一种是filesort(基于快排实现的外部排序,性能比较差),当数据量很大时,这将是一个CPU密集型的过程,所以可以通过建立合适的索引来优化排序的性能

对explain执行计划详请感兴趣的读者可以阅读这篇文章:https://blog.csdn.net/a745233700/article/details/84335453

10、MySQL的主从复制:

10.1、MySQL主从复制的原理:

Slave从Master获取bin log二进制日志文件,然后再将日志文件解析成相应的SQL语句在从服务器上重新执行一遍主服务器的操作,通过这种方式来保证数据的一致性。由于主从复制的过程是异步复制的,因此Slave和Master之间的数据有可能存在延迟的现象,只能保证数据最终的一致性。在master和slave之间实现整个复制过程主要由三个线程来完成:

  • (1)Slave SQL thread线程:创建用于读取relay log中继日志并执行日志中包含的更新,位于slave端
  • (2)Slave I/O thread线程:读取 master 服务器Bin log Dump线程发送的内容并保存到slave服务器的relay log中继日志中,位于slave端:
  • (3)Bin log dump thread线程(也称为IO线程):将bin-log二进制日志中的内容发送到slave服务器,位于master端

注意:如果一台主服务器配两台从服务器那主服务器上就会有两个Bin log dump 线程,而每个从服务器上各自有两个线程;

10.2、主从复制流程:

  • (1)master服务器在执行SQL语句之后,记录在bin log二进制文件中;
  • (2)slave端的IO线程连接上master端,并请求从指定bin log日志文件的指定pos节点位置(或者从最开始的日志)开始复制之后的日志内容。
  • (3)master端在接收到来自slave端的IO线程请求后,通知负责复制进程的IO线程,根据slave端IO线程的请求信息,读取指定bin log日志指定pos节点位置之后的日志信息,然后返回给slave端的IO线程。该返回信息中除了bin log日志所包含的信息之外,还包括本次返回的信息在master端的bin log文件名以及在该bin log日志中的pos节点位置。
  • (4)slave端的IO线程在接收到master端IO返回的信息后,将接收到的bin log日志内容依次写入到slave端的relay log文件的最末端,并将读取到的master端的bin log文件名和pos节点位置记录到master-info文件中(该文件存slave端),以便在下一次同步的候能够告诉master从哪个位置开始进行数据同步;
  • (5)slave端的SQL线程在检测到relay log文件中新增内容后,就马上解析该relay log文件中的内容,然后还原成在master端真实执行的那些SQL语句,再按顺序依次执行这些SQL语句,从而到达master端和slave端的数据一致性;

10.3、主从复制的好处:

  • (1)读写分离,通过动态增加从服务器来提高数据库的性能,在主服务器上执行写入和更新,在从服务器上执行读功能。
  • (2)提高数据安全,因为数据已复制到从服务器,从服务器可以终止复制进程,所以,可以在从服务器上备份而不破坏主服务器相应数据。
  • (3)在主服务器上生成实时数据,而在从服务器上分析这些数据,从而提高主服务器的性能。

10.4、MySQL支持的复制类型及其优缺点:

bin log日志文件有两种格式,一种是Statement-Based(基于语句的复制),另一种是Row-Based(基于行的复制)。默认格式为Statement-Based,如果想改变其格式在开启服务的时候使用 -binlog-format 选项,其具体命令如下:

mysqld_safe –user=msyql –binlog-format=格式 &

(1)基于语句的复制(Statement-Based):在主服务器上执行的SQL语句,在从服务器上执行同样的语句。效率比较高。 一旦发现没法精确复制时,会自动选着基于行的复制。

优点:

  • ① 因为记录的SQL语句,所以占用更少的存储空间。binlog日志包含了描述数据库操作的事件,但这些事件包含的情况只是对数据库进行改变的操作,例如 insert、update、create、delete等操作。相反对于select、desc等类似的操作并不会去记录。
  • ② binlog日志文件记录了所有的改变数据库的语句,所以此文件可以作为数据库的审核依据。

缺点:

  • ① 不安全,不是所有的改变数据的语句都会被记录。对于非确定性的行为不会被记录。例如:对于 delete 或者 update 语句,如果使用了 limit 但是并没有 order by ,这就属于非确定性的语句,就不会被记录。
  • ② 对于没有索引条件的update,insert……select 语句,必须锁定更多的数据,降低了数据库的性能。

(2)基于行的复制(Row-Based):把改变的内容复制过去,而不是把命令在从服务器上执行一遍,从mysql5.0开始支持;

优点:

  • ① 所有的改变都会被复制,这是最安全的复制方式;
  • ② 对于 update、insert……select等语句锁定更少的行;

缺点:

  • ① 不能通过binlog日志文件查看什么语句执行了,也无从知道在从服务器上接收到什么语句,我们只能看到什么数据改变。
  • ② 因为记录的是数据,所以说binlog日志文件占用的存储空间要比Statement-based大。
  • ③ 对于数据量大的操作其花费的时间有更长。

(3)混合类型的复制:默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。

有关主从复制更详细的内容,请阅读这篇文章:https://blog.csdn.net/a745233700/article/details/85256818

11、读写分离:

11.1、实现原理:

读写分离解决的是,数据库的写操作,影响了查询的效率,适用于读远大于写的场景。读写分离的实现基础是主从复制,主数据库利用主从复制将自身数据的改变同步到从数据库集群中,然后主数据库负责处理写操作(当然也可以执行读操作),从数据库负责处理读操作,不能执行写操作。并可以根据压力情况,部署多个从数据库提高读操作的速度,减少主数据库的压力,提高系统总体的性能。

11.2、读写分离提高性能的原因:

  • (1)增加物理服务器,负荷分摊;
  • (2)主从只负责各自的写和读,极大程度的缓解X锁和S锁争用;
  • (3)从库可配置MyISAM引擎,提升查询性能以及节约系统开销;
  • (4)主从复制另外一大功能是增加冗余,提高可用性,当一台数据库服务器宕机后能通过调整另外一台从库来以最快的速度恢复服务。

11.3、Mysql读写分写的实现方式:

  • (1)基于程序代码内部实现:在代码中根据select 、insert进行路由分类。优点是性能较好,因为程序在代码中实现,不需要增加额外的硬件开支,缺点是需要开发人员来实现,运维人员无从下手。
  • (2)基于中间代理层实现:代理一般介于应用服务器和数据库服务器之间,代理数据库服务器接收到应用服务器的请求后根据判断后转发到后端数据库,有以下代表性的代理层。

12、分库分表:

读写分离解决的是数据库读写操作的压力,但是没有分散数据库的存储压力,利用分库分表可以解决数据库的储存瓶颈,并提升数据库的查询效率。

12.1、垂直拆分:

(1)垂直分表:将一个表按照字段分成多个表,每个表存储其中一部分字段。一般会将常用的字段放到一个表中,将不常用的字段放到另一个表中。

优点:

  • (1)避免IO竞争减少锁表的概率。因为大的字段效率更低,第一,大字段占用的空间更大,单页内存储的行数变少,会使得IO操作增多;第二数据量大,需要的读取时间长。
  • (2)可以更好地提升热门数据的查询效率。

(2)垂直分库:按照业务模块的不同,将表拆分到不同的数据库中,适合业务之间的耦合度非常低、业务逻辑清晰的系统。

优点:

  • 降低业务中的耦合,方便对不同的业务进行分级管理
  • 可以提升IO、数据库连接数、解决单机硬件存储资源的瓶颈问题

(3)垂直拆分(分库、分表)的缺点:

  • 主键出现冗余,需要管理冗余列
  • 事务的处理变得复杂
  • 仍然存在单表数据量过大的问题

12.2、水平拆分:

(1)水平分表:在同一个数据库内,把同一个表的数据按照一定规则拆分到多个表中。

优点:

  • 解决了单表数据量过大的问题
  • 避免IO竞争并减少锁表的概率

(2)水平分库:把同一个表的数据按照一定规则拆分到不同的数据库中,不同的数据库可以放到不同的服务器上。

优点:

  • 解决了单库大数据量的瓶颈问题
  • IO冲突减少,锁的竞争减少,某个数据库出现问题不影响其他数据库,提高了系统的稳定性和可用性

(3)水平拆分(分表、分库)的缺点:

  • 分片事务一致性难以解决
  • 跨节点JOIN性能差,逻辑会变得复杂
  • 数据扩展难度大,不易维护

12.3、分库分表存在的问题的解决:

(1)事务的问题:

① 方案一:使用分布式事务:

  • 优点:由数据库管理,简单有效。
  • 缺点:性能代价高,特别是shard越来越多。

② 方案二:程序与数据库共同控制实现,原理就是将一个跨多个数据库的分布式事务分解成多个仅存在于单一数据库上面的小事务,并交由应用程序来总体控制各个小事务。

  • 优点:性能上有优势;
  • 缺点:需要在应用程序在事务上做灵活控制。如果使用了spring的事务管理,改动起来会面临一定的困难。

(2)跨节点 Join 的问题:

解决该问题的普遍做法是分两次查询实现:在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。

(3)跨节点count,order by,group by,分页和聚合函数问题:

由于这类问题都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作,解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个结点的查询可以并行执行,因此速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。

12.4、分库分表后,ID键如何处理?

分库分表后不能每个表的ID都是从1开始,所以需要一个全局ID,设置全局ID主要有以下几种方法:

(1)UUID:

  • 优点:本地生成ID,不需要远程调用,全局唯一不重复。
  • 缺点:占用空间大,不适合作为索引。

(2)数据库自增ID:在分库分表表后使用数据库自增ID,需要一个专门用于生成主键的库,每次服务接收到请求,先向这个库中插入一条没有意义的数据,获取一个数据库自增的ID,利用这个ID去分库分表中写数据。

  • 优点:简单易实现。
  • 缺点:在高并发下存在瓶颈。

(3)Redis生成ID:

  • 优点:不依赖数据库,性能比较好。
  • 缺点:引入新的组件会使得系统复杂度增加

(4)Twitter的snowflake算法:是一个64位的long型的ID,其中有1bit是不用的,41bit作为毫秒数,10bit作为工作机器ID,12bit作为序列号。

  • 1bit:第一个bit默认为0,因为二进制中第一个bit为1的话为负数,但是ID不能为负数.
  • 41bit:表示的是时间戳,单位是毫秒。
  • 10bit:记录工作机器ID,其中5个bit表示机房ID,5个bit表示机器ID。
  • 12bit:用来记录同一毫秒内产生的不同ID。

(5)美团的Leaf分布式ID生成系统,美团点评分布式ID生成系统

13、分区:

分区就是将表的数据按照特定规则存放在不同的区域,也就是将表的数据文件分割成多个小块,在查询数据的时候,只要知道数据数据存储在哪些区域,然后直接在对应的区域进行查询,不需要对表数据进行全部的查询,提高查询的性能。同时,如果表数据特别大,一个磁盘磁盘放不下时,我们也可以将数据分配到不同的磁盘去,解决存储瓶颈的问题,利用多个磁盘,也能够提高磁盘的IO效率,提高数据库的性能。在使用分区表时,需要注意分区字段必须放在主键或者唯一索引中、每个表最大分区数为1024;常见的分区类型有:Range分区、List分区、Hash分区、Key分区,

  • (1)Range分区:按照连续的区间范围进行分区
  • (2)List分区:按照给定的集合中的值进行选择分区。
  • (3)Hash分区:基于用户定义的表达式的返回值进行分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式。
  • (4)Key分区:类似于按照HASH分区,区别在于Key分区只支持计算一列或多列,且key分区的哈希函数是由 MySQL 服务器提供。

(1)表分区的优点:

① 可伸缩性:

  • 将分区分在不同磁盘,可以解决单磁盘容量瓶颈问题,存储更多的数据,也能解决单磁盘的IO瓶颈问题。

② 提升数据库的性能:

  • 减少数据库检索时需要遍历的数据量,在查询时只需要在数据对应的分区进行查询。
  • 避免Innodb的单个索引的互斥访问限制
  • 对于聚合函数,例如sum()和count(),可以在每个分区进行并行处理,最终只需要统计所有分区得到的结果

③ 方便对数据进行运维管理:

  • 方便管理,对于失去保存意义的数据,通过删除对应的分区,达到快速删除的作用。比如删除某一时间的历史数据,直接执行truncate,或者直接drop整个分区,这比detele删除效率更高;
  • 在某些场景下,单个分区表的备份很恢复会更有效率。

14、主键一般用自增ID还是UUID?

(1)自增ID:

使用自增ID的好处:

  • 字段长度较 UUID 会小很多。
  • 数据库自动编号,按顺序存放,利于检索
  • 无需担心主键重复问题

使用自增ID的缺点:

  • 因为是自增,在某些业务场景下,容易被其他人查到业务量。
  • 发生数据迁移时,或者表合并时会非常麻烦
  • 在高并发的场景下,竞争自增锁会降低数据库的吞吐能力

(2)UUID:通用唯一标识码,UUID是基于当前时间、计数器和硬件标识等数据计算生成的。

使用UUID的优点:

  • 唯一标识,不用考虑重复问题,在数据拆分、合并时也能达到全局的唯一性。
  • 可以在应用层生成,提高数据库的吞吐能力。
  • 无需担心业务量泄露的问题。

使用UUID的缺点:

  • 因为UUID是随机生成的,所以会发生随机IO,影响插入速度,并且会造成硬盘的使用率较低。
  • UUID占用空间较大,建立的索引越多,造成的影响越大。
  • UUID之间比较大小较自增ID慢不少,影响查询速度。

一般情况下,MySQL推荐使用自增ID,因为在MySQL的 InnoDB 存储引擎中,主键索引是聚簇索引,主键索引的B+树的叶子节点按照顺序存储了主键值及数据,如果主键索引是自增ID,只需要按顺序往后排列即可,如果是UUID,ID是随机生成的,在数据插入时会造成大量的数据移动,产生大量的内存碎片,造成插入性能的下降。

15、视图View:

视图是从一个或者多个表(或视图)导出的表,其内容由查询定义。视图是一个虚拟表,数据库中只存储视图的定义,不存储视图对应的数据,在对视图的数据进行操作时,系统根据视图的定义去操作相应的基本表。可以说,视图是在基本表之上建立的表,它的结构和内容都来自基本表,依据基本表存在而存在。一个视图可以对应一个基本表,也可以对应多个基本表。视图是基本表的抽象和在逻辑意义上建立的新关系。

(1)视图的优点:

  • 简化了操作,把经常使用的数据定义为视图
  • 安全性,用户只能查询和修改能看到的数据
  • 逻辑上的独立性,屏蔽了真实表的结构带来的影响

(2)视图的缺点:

  • 性能差,数据库必须把对视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也要把它变成一个复杂的结合体,需要花费一定的时间。

16、存储过程Procedure:

SQL语句需要先编译然后执行,而存储过程就是一组为了完成特定功能的SQL语句集,经过编译后存储在数据库中,用户通过制定存储过程的名字并给定参数来调用它。

用程序也可以实现操作数据库的复杂逻辑,那为什么需要存储过程呢?主要是因为使用程序调用API执行,其效率相对较慢,应用程序需通过引擎把SQL语句交给MYSQL引擎来执行,那还不如直接让MySQL负责它最精通最能够完成的工作。

存储过程的优点:

  • (1)标准组件式编程:存储过程创建后,可以在程序中被多次调用,而不必重新编写该存储过程的SQL语句。并且DBA可以随时对存储过程进行修改,对应用程序源代码毫无影响。
  • (2)更快的执行速度:如果某一操作包含大量的Transaction-SQL代码或分别被多次执行,那么存储过程要比批处理的执行速度快很多。因为存储过程是预编译的,在首次运行一个存储过程时查询,优化器对其进行分析优化,并且给出最终被存储在系统表中的执行计划。而批处理的Transaction-SQL语句在每次运行时都要进行编译和优化,速度相对要慢一些。
  • (3)增强SQL语言的功能和灵活性:存储过程可以用控制语句编写,有很强的灵活性,可以完成复杂的判杂的断和较复运算。
  • (4)减少网络流量:针对同一个数据库对象的操作(如查询、修改),如果这一操作所涉及的Transaction-SQL语句被组织进存储过程,那么当在客户计算机上调用该存储过程时,网络中传送的只是该调用语句,从而大大减少网络流量并降低了网络负载。
  • (5)作为一种安全机制来充分利用:通过对执行某一存储过程的权限进行限制,能够实现对相应的数据的访问权限的限制,避免了非授权用户对数据的访问,保证了数据的安全。

17、触发器Trigger:

触发器是与表有关的数据库对象,当触发器所在表上出现指定事件并满足定义条件的时候,将执行触发器中定义的语句集合。触发器的特性可以应用在数据库端确保数据的完整性。触发器是一个特殊的存储过程,不同的是存储过程要用call来调用,而触发器不需要使用call,也不需要手工调用,它在插入,删除或修改特定表中的数据时触发执行,它比数据库本身标准的功能有更精细和更复杂的数据控制能力。

18、游标Cursor:

游标,就是游动的标识,可以充当指针的作用,使用游标可以遍历查询数据库返回的结果集中的所有记录,但是每次只能提取一条记录,即每次只能指向并取出一行的数据,以便进行相应的操作。当你没有使用游标的时候,相当于别人一下给你所有的东西让你拿走;用了游标之后,相当于别人一件一件的给你,这时你可以先看看这个东西好不好,再自己进行选择。

全部评论

相关推荐

许愿ssp的咸鱼很不想泡池子:import python as pyhton
点赞 评论 收藏
分享
评论
11
57
分享

创作者周榜

更多
牛客网
牛客企业服务