牛客网、leetcode面经整理
操作系统
同步、异步,阻塞、非阻塞
- 同步:一个同步调用发出后,调用者要等待返回结果,才能进行后续的执行
- 异步:一个异步过程调用发出后,调用者不能立刻得到返回结果,处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者(同步异步更关注消息通知机制)
- 阻塞:调用结果返回前,当前线程会被挂起
- 非阻塞:即使调用结果没返回,也不会阻塞当前线程
- 同步阻塞:小明去点火烧水(发消息),在烧水过程中,小明啥也不干,端个板凳坐着傻等(阻塞),等水开后,小明才能做下一步处理(同步)
- 同步非阻塞:小明去点火烧水(发消息),在烧水过程中,小明跑去看电视了(非阻塞),但它时不时查看一下水烧开没有,最后等水开了,小明才能做下一步处理(同步)——小明还是要亲自去看水是否烧开没有,即同步
- 异步阻塞:小明换了个带有铃铛的水壶,小明去点火烧水(发消息),在烧水过程中,小明啥也不干,端个板凳坐着傻等(阻塞),等水开后,铃铛自动会响通知小明水已经烧好(异步)
- 异步非阻塞:小明换了个带有铃铛的水壶,小明去点火烧水(发消息),在烧水过程中,小明跑去看电视了(非阻塞),等水开后,铃铛自动会响通知小明水已经烧好(异步)
进程控制块(PCB)
- 包含:
- 进程描述信息:
- 进程标识符:标识各个进程
- 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务;
- 进程控制和管理信息
- 进程当前状态(new、ready、running、waiting 或 blocked)
- 进程优先级:抢占 CPU 时的优先级
- 资源分配清单:内存地址空间或虚拟地址空间的信息,打开文件列表和使用的 I/O 设备信息
- CPU 相关信息:CPU 中各个寄存器的值(进程切换时,CPU 的状态信息会被保存在相应的 PCB 中,以便重新执行时,从断点处恢复)
- 进程描述信息:
- 结构:
- 通过链表的形式组织(相同状态的进程链在一起,形成队列)——就绪队列、不同等待事件的阻塞队列
- 运行队列在单核 CPU 系统中只有一个运行指针,因为一个时间只能运行一个程序
进程和线程的区别
- 进程(Process)是系统进行资源分配和调度的基本单位,线程(Thread)是CPU调度和分派的基本单位
- 一个进程至少有一个线程
- 进程的地址空间独立,线程共享所属进程的地址空间
- 线程自己基本不拥有系统资源,和其他线程共享本进程的相关资源如内存、I/O、cpu等
- 进程切换涉及到当前进程CPU环境的保存和新进程CPU环境的设置,线程切换只需保存和设置少量寄存器——切换进程开销远大于切换线程开销
- 线程的通信方便(通过共享全局变量数据),进程之间的通信需要以进程间通信的方式进行
- 多线程程序只要有一个线程崩溃,整个程序就崩溃了,但多进程程序中一个进程崩溃并不会对其它进程造成影响,因为进程有自己的独立地址空间,因此多进程更加健壮
进程状态转换
- 三种状态:就绪态、运行态和阻塞态
- 就绪状态:进程获得除cpu外的所需资源,等待分配cpu资源
- 运行状态:占用cpu运行
- 阻塞状态: 进程等待某条件,条件满足前无法执行
进程切换的场景
- 时间片到了:CPU 时间被划分为时间片,分配给各个进程。某个进程的时间片耗尽,进程从运行状态变为就绪状态,系统从就绪队列选择另外一个进程运行
- 等待资源:进程在资源不足时被挂起,由系统调度其他进程运行
- 主动挂起:进程通过睡眠函数 sleep 等将自己主动挂起
- 优先级抢占:有优先级更高的进程运行时,当前进程被挂起
- 中断:发生硬件中断时,CPU 上的进程会被中断挂起,执行内核的中断服务程序
进程间通信(IPC)
管道
- 半双工,具有固定的读端和写端
- 父子进程、兄弟进程之间通信
- 视为一种特殊的文件,读写可以用普通的 read、write 等函数,但它不属于文件系统,只存在于内存中
- 命名管道
- FIFO 可以在无关的进程之间交换数据
- FIFO 有路径名与之相关联,以一种特殊设备文件形式存在于文件系统
消息队列:消息的链接表,存放在内核,一个消息队列由一个标识符 ID 来标识
- 面向记录,消息具有特定的格式和特定的优先级
- 独立于发送与接收进程,进程终止时消息队列及其内容不会被删除
- 可以实现消息的随机查询
- 消息不一定要以FIFO读取,也可以按消息的类型读取
共享内存:两个或多个进程共享一个给定的存储区
- 最快的一种 IPC——进程直接对内存进行存取
- 信号量:是一个计数器,实现进程间的互斥与同步,不存储进程间通信数据,若要传递数据需要结合共享内存
- 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作;
- 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数;
- 支持信号量组
- 信号(signal/kill)
- 进程间通信机制中唯一的异步通信机制
- 处理方式:执行默认操作、捕捉信号、忽略信号
- 通知接收进程某个事件已经发生
- 套接字(Socket)
- 不同主机间进程通信、同一主机中进程通信
- 系统调用:
int socket(int domain, int type, int protocal)
- domain:指定协议族,
AF_INET
用于 IPV4、AF_INET6
用于 IPV6、AF_LOCAL/AF_UNIX
用于本机 - type:指定通信特性,
SOCK_STREAM
表示字节流(对应TCP)、SOCK_DGRAM
表示数据报(对应UDP)、SOCK_RAW
表示原始套接字 - protocal:用来指定通信协议,现在基本废弃(前两个参数指定即可),一般为0
- domain:指定协议族,
- 通信方式
TCP 字节流通信
UDP 数据报通信
本地进程间通信:本地字节流 socket 参数是
AF_LOCAL
和SOCK_STREAM
,本地数据报 socket 参数是AF_LOCAL
和SOCK_DGRAM
线程同步方式
- 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限
- 信号量(Semaphore) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
- 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步
进程调度
- 批处理系统
- 先来先服务
- 最短作业优先
- 最短剩余时间优先
- 最高响应比优先
- 交互式系统
- 时间片轮转:每个进程被分配一个时间段
- 优先级调度算法
- 多级反馈队列调度:N个队列,各个队列的时间片是随着优先级的增加而减少
死锁
- 某进程集合中,每个进程都等待只能由该进程集合的其他进程引发的事件,该进程集合是死锁的
- 资源死锁是竞争性同步问题,通信死锁是协同同步问题
- 必要条件
- 互斥:在一段时间内某资源仅为一进程所占用
- 占用并等待:当进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不可抢占:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放
- 环路等待:在发生死锁时,必然存在一个进程–资源的环形链
- 解决
- 预防
- 破坏请求条件:一次性分配所有资源
- 破坏请保持条件:只要一个资源不被分配,其他资源也不被分配给该进程
- 破坏不可剥夺条件:某进程获得了部分资源,但得不到其它资源,则释放已占有的资源
- 破坏环路等待条件:每类资源编号,进程按编号递增的顺序请求资源,释放则相反
- 避免:银行家算法
- 解除
- 资源剥夺:挂起某些死锁进程,剥夺其资源,分配给其他死锁进程
- 撤销进程:强制撤销部分、甚至全部死锁进程并剥夺进程的资源——按进程优先级和撤销进程代价的高低进行
- 进程回退:一个或多个进程回退到足以避免死锁的地步,系统保持进程的历史信息,设置还原点
- 预防
典型的锁
- 读写锁
- 多个读者同时读
- 写者互斥(一个写者写,不能读写同时进行)
- 写者优先读者(一旦有写,则后续读必须等待,唤醒时优先考虑写)
- 互斥锁:
- 一次只能一个线程拥有互斥锁
- 抢锁失败的线程进入睡眠状态,直到锁的状态改变时再唤醒
- 为了实现锁的状态改变时唤醒阻塞的线程或者进程,互斥锁由操作系统管理,因此在加锁操作时涉及上下文的切换
- 条件变量(信号量)
- 互斥锁只有两种状态:锁定和非锁定
- 条件不满足时,线程解开相应的互斥锁并阻塞线程然后等待条件变化。一旦其他线程改变了条件变量,将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程
- 互斥锁是线程间互斥的机制,条件变量是同步机制
- 自旋锁
- 进线程无法取得锁时,不会立刻放弃CPU,而是一直循环尝试获取锁,直到获取为止——容易浪费CPU资源
- 一般应用于加锁时间很短的场景
分页与分段的区别?
- 三种内存存储
- 页式存储:用户地址空间划分为大小相等的页(page),内存空间划分为同样大小的页框,分配时以页为单位,按进程需要的页数分配
- 段式存储:用户地址空间按照自身逻辑关系划分为若干segment(代码段,数据段,堆栈段),内存空间被动态划分为长度不同的区域,分配时以段为单位,每段在内存中占据连续空间,各段可以不相邻
- 段页式存储:用户进程先按段划分,段内再按页划分,内存划分和分配按页
- 区别:
- 目的不同:
- 分页的目的是管理内存,用于虚拟内存以获得更大地址空间
- 分段的目的是满足用户的需要,使程序和数据可以被划分为逻辑上独立的地址空间
- 大小不同:段的大小不固定,由相应功能决定;页的大小固定,由系统决定
- 地址空间维度不同:
- 分段是二维地址空间(段号+段内偏移)
- 分页是一维地址空间(每个进程一个页表/多级页表,通过一个逻辑地址就能找到对应的物理地址)
- 分段便于信息的保护和共享;分页的共享受限
- 碎片:
- 分段没有内碎片,会产生外碎片(不属于当前进程的闲置空间)
- 分页没有外碎片,会产生内碎片(一个页填不满)
- 目的不同:
详细解析:为什么要有虚拟内存
物理地址、逻辑地址、虚拟内存
物理地址:地址转换的最终地址,是内存单元真正的地址
逻辑地址:计算机用户看到的地址,如一个长度为100的整型数组,OS返回一个逻辑上的连续空间——OS通过地址映射将物理地址映射成连续的逻辑地址
虚拟内存:计算机系统内存管理的一种技术,使得应用程序认为它拥有连续的可用的内存(连续完整的地址空间),实际地址空间被分隔成多个物理内存碎片,甚至部分存储在磁盘,需要时进行数据交换
- 地址空间到物理内存的映射:
- 内存管理单元(MMU)管理逻辑地址和物理地址的转换
- MMU中的页表(Page table)存储页(逻辑地址)和页框(物理内存空间)的映射表
- 逻辑地址:页号+页内偏移;一个进程一个页表,页表起始地址在PCB中
页面置换算法
- 先进先出置换(FIFO):淘汰最早调入的页面
- 最佳置换(OPT):淘汰未来最远将使用的页,最优的方案但不可行
- 最近最久未使用(LRU):淘汰最近最久未使用的页面
- 时钟(Clock)置换(最近未用,NRU):每个页面设置一位访问位,内存中的所有页面都通过链接指针链成一个循环队列
- 每一页设置一个访问位,将内存中所有页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位为1。淘汰时,检查页的访问位。若是0,则将该页换出;若是1,则重新将它置为0,暂时不换出,接着按照FIFO的算法检查下一个页面。当检查到队列中最后一个页面时,其访问位仍为1,则再放回队首,检查下一个页面
- 最不经常使用置换(NFU):淘汰访问次数最少的页面
动态链接库和静态链接库
- 静态链接:编译链接时将需要的执行代码拷贝到调用处。优点:不需要带着库一起发布程序;缺点:体积相对大
- 动态链接:编译链接时不直接拷贝可执行代码,在程序运行或加载时OS将需要的动态库加载到内存,程序运行到指定的代码时去共享执行内存中寻找动态库。优点:多个程序共享同一段代码;缺点:影响程序的前期执行性能
进程的异常控制流:陷阱、中断、异常和信号
- 陷阱:
- 同步异常,是执行一条指令的结果
- 主要作用是实现系统调用:中断当前的控制流,陷入到内核态,执行相应的系统调用,处理结束后将结果返回给进程,进程继续执行下一条指令
- 中断:
- 处理器外部硬件产生,不是执行某条指令的结果——中断是异步事件
- 中断包括I/O中断(I/O设备)、时钟中断(各种定时器)等
- 异常:是执行当前指令产生的错误情况,可能被错误处理程序修正,也可能直接终止应用程序——是同步的
- 信号:
- 一种更高层的软件形式的异常,会中断进程的控制流,可以由进程处理
- 一个信号代表了一个消息,用来通知进程发生了某种系统事件
用户态和内核态
操作系统的两种运行状态
- 内核态:内核态的CPU可以访问任意数据,可以从一个程序切换到另外一个程序,占用CPU不会发生抢占,一般处于特权级0
- 用户态:用户态的CPU只能访问部分内存,不允许访问外围设备,不允许独占,能够被其他程序获取。用户程序都运行在用户态,进行内核态的操作(从硬盘、键盘读数据)时,进行系统调用,使用陷阱指令CPU切换到内核态,执行相应的服务后切换回用户态并返回系统调用的结果
守护进程、僵尸进程和孤儿进程
- 守护进程:
- 在后台运行的,没有控制终端与之相连的进程,独立于控制终端,周期性执行某种任务
- Linux大多数服务器用守护进程的方式实现,如web服务器进程http
- 僵尸进程
- 一个子进程结束后,它的父进程没有等待它(调用wait或者waitpid),子进程将成为一个僵尸进程
- 已经死亡但没有真正被销毁的进程,没有任何可执行代码,不能被调度,占用进程表的一个位置(记载该进程的进程ID、终止状态、资源利用信息——CPU时间,内存使用量等等)供父进程收集
- 危害:占用进程号(进程号有限),占用内存
- 解决:
- 进程结束时,系统会扫描是否其存在子进程,如果有则用Init进程接管,Init成为该进程的父进程,调用wait等待其结束
- 父进程调用wait或者waitpid等待子进程结束(每隔一段时间查询子进程是否结束):
- wait系统调用会使父进程暂停执行,直到一个子进程结束为止
- waitpid系统调用可以加入WNOHANG(wait-no-hang)选项,如果没有结束的子进程则立即返回。waitpid可以选择:等待任一子进程(同wait)、等待指定pid的子进程、等待同一进程组下的任一子进程、等待组ID等于pid的任一子进程
- 子进程结束时,系统产生SIGCHLD(signal-child)信号,可以注册一个信号处理函数,在该函数中调用waitpid,等待所有结束的子进程——一般需要循环调用waitpid,因为在信号处理函数开始执行之前,可能已经有多个子进程结束;可以用
signal(SIGCLD, SIG_IGN)
(signal-ignore)通知内核,表示父进程忽略SIGCHLD信号,子进程结束后内核会进行回收
- 孤儿进程:父进程结束,其子进程还在运行,这些子进程将成为孤儿进程。会被Init(进程ID为1)接管,孤儿进程结束时由Init完成状态收集工作
IO
- 文件系统的默认IO操作是缓存IO。OS将IO的数据缓存在文件系统的页缓存(page cache)——数据先被拷贝到内核的缓冲区中,再从内核缓存区拷贝到应用程序的地址空间——因为应用程序不能直接操作底层硬件
- IO阶段:数据准备阶段 + 内核空间复制回用户进程缓冲区阶段
检测下
- 说下进程的状态
- 说下进程和线程的联系与区别
- 为什么进程上下文切换比线程上下文切换代价高?
- 说下你对进程同步的理解
- 对多个相关进程在执行次序上进行协调,以使并发执行的诸进程之间能有效地共享资源和相互合作
- 同步方式:临界区、互斥锁、信号量、事件(Event)
- 进程的通信方式有哪些
- 进程调度的种类有哪些?
- 非抢占式调度与抢占式调度的区别是什么?
- 让进程运行直到结束或阻塞的调度方式
- 允许将逻辑上可继续运行的在运行过程暂停的调度方式
- 说下你知道的调度算法
- 一个程序从开始运行到结束的完整过程(四个过程)
- 死锁出现的条件?
- 如何处理死锁问题
- 什么是临界资源
- 介绍一下内存池、进程池、线程池
- 动态链接库与静态链接库的区别
- 说下对虚拟内存的理解
- 页面置换算法了解多少?
- 中断与系统调用了解吗?
- 中断:在计算机运行过程中,当发生某个事件后,CPU 会停止当前程序流,转而去处理该事件,并在处理完毕后继续执行原程序流
- 软中断是一条 CPU 指令,由当前正在运行的进程产生,软中断模拟了硬中断的处理过程。系统调用是一种软中断处理程序,用于让程序从用户态陷入内核态,以执行相应的操作
- 用户态切换到内核态的方式有哪些?
- 用户态和核心态(内核态)之间的区别是什么呢?
- 内部碎片与外部碎片分别是什么?
- 系统调用与库函数的区别
- 守护、僵尸、孤儿进程的概念
计算机网络
OSI与TCP/IP体系结构
- OSI:七层,物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
- TCP/IP:应用层(Telnet、FTP、SMTP)、传输层(TCP、UDP)、网络层(IP协议)、网络接口层
- TCP/IP四层是OSI七层的简化,是目前的标准
HTTP,HTTPS,Get和Post
HTTP:超文本传输协议,定义计算机之间交流通信的规范和各种控制和错误处理方式
- 状态码:
- 1xx:提示信息,协议处理的中间状态
- 2xx:成功,报文收到并正确处理
- 200:请求成功(一般用于GET与POST请求)
- 201:Created,已创建。成功请求并创建了新的资源
- 202:Accepted,已接受。已经接受请求,但未处理完成
- 204:No Content,服务器成功处理,但未返回内容
- 206:Partial Content,服务器成功处理了部分GET请求
- 3xx:重定向,资源位置发生变动,需要客户端重发请求
- 301:永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI
- 302:Found,临时移动。与301类似。但资源只是临时被移动,客户端应继续使用原有URI
- 304:Not Modified,所请求的资源未修改,服务器返回此状态码时,不会返回任何资源(浏览器从缓存中检索网页的保存版本,防止浏览器重复下载相同的信息)
- 305:Use Proxy,所请求的资源必须通过代理访问,代理地址在 Response 的 Location 中
- 4xx:客户端错误,请求报文有误
- 400:Bad Request,客户端请求的语法错误,服务器无法理解
- 401:未授权,要求身份验证
- 403:Forbidden,服务器拒绝执行此请求(理解请求含义)
- 404:Not Found,无法根据客户端的请求找到资源(网页)
- 5xx:服务器错误,服务器处理请求时内部出现问题
- 500:Internal Server Error,服务器内部错误,无法完成请求
- 501:Not Implemented,服务器不支持请求的功能
- 502:Bad Gateway,网关或者代理服务器执行请求时,从远程服务器收到一个无效响应
- 503:Service Unavailable,服务器暂时的无法处理客户端的请求
转发和重定向的区别:
转发是服务器行为,服务器向目标地址访问URL,读取相应内容并发给浏览器,转发页面和转发到的页面可以共享request里面的数据
重定向利用服务器返回的状态码实现。如果服务器返回301或302,浏览器会自动跳转到新的网址,重新请求资源,用户的地址栏URL发生改变
Get和Post区别
- get获取数据,post提交数据
- get无副作用,幂等,可缓存,所发送的数据或参数是 URL 的一部分,有长度限制(服务器处理长 URL 要消耗比较多的资源);post相反,无幂等性(URI是去执行创建动作的操作者,例如post一个帖子,两次post会创建两个帖子,两次响应中包含帖子的创建状态以及帖子的不同URI),post参数放在 body 中
- HTTP优缺点:
- 优点:简单、易于拓展、跨平台
- 缺点:无状态、明文传输、不安全
HTTP短连接和长连接:
- 短连接:客户端与服务器进行一次HTTP连接,连接结束TCP关闭连接。
- 长连接:HTTP头部带有参数keep-alive,底层用于传输数据的TCP连接不会直接关闭,会根据服务器设置的保持时间保持连接,保持时间过后连接关闭
- HTTP1.1相对于HTTP1.0的优化:
- 长连接;HTTP/1.0默认使用短连接,每次请求要重新建立一次连接;HTTP 1.1默认使用长连接
- 错误状态响应码:HTTP1.1新增多个错误状态响应码
- 缓存处理:HTTP1.1引入更多的缓存控制策略
- 带宽优化及网络连接的使用:HTTP1.0在一些浪费带宽的现象,例如客户端可能需要某个对象的一部分,服务器传回整个对象,且不支持断点续传。HTTP1.1在请求头引入了range头域,允许只请求资源的某个部分,返回码是206
- HTTPS:
- HTTP明文传输,HTTPS在TCP和HTTP之间加SSL/TLS安全协议,报文加密传输——SSL:安全套接层,TLS:传输层安全协议
- 混合加密实现机密性,防止窃听
- 摘要实现完整性,防止篡改
- 服务器公钥放入数字证书实现不可抵赖性,防止冒充
- 先有SSL,之后SSL升级为TLS;TLS 1.0通常被标示为SSL 3.1,TLS 1.1为SSL 3.2,TLS 1.2为SSL 3.3
- HTTP进行TCP三次握手后便可进行报文传输,HTTPS在TCP三次握手后需要建立SSL连接:
- 三次握手后还需SSL/TLS的握⼿
- 浏览器发起第一个HTTPS请求(tcp第三次握手中进行,因为前两次握手没有数据传输),给服务器发送支持的加密算法
- 服务器据此选择一套加密算法,以证书的形式返回给浏览器
- 客户端(SSL/TLS)解析证书,验证证书合法性,生成对称加密密钥client key,用服务器的公钥非对称加密客户端密钥;发起HTTPS中的第二个HTTP请求,发给服务器加密的client key
- 服务器用私钥解密,用解密后的客户端密钥加密数据,发回给客户端
- 客户端解密密文,得到服务器的数据。整个HTTPS传输完成
- HTTP端口号是 80,HTTPS端口号是443
- HTTPS要向CA申请数字证书
- HTTP明文传输,HTTPS在TCP和HTTP之间加SSL/TLS安全协议,报文加密传输——SSL:安全套接层,TLS:传输层安全协议
HTTP1.1、HTTP2、HTTP3
- HTTP1.1性能瓶颈
- 请求/响应头部未经压缩就发送
- 服务器按请求顺序响应,如果服务器响应慢,会导致消息队头阻塞
- 无没有请求优先级控制
- 服务器只能被动响应
- HTTP2改进(15年提出):
- 头部压缩、二进制格式、数据流
- 多路复用:此前文件串行传输,请求a文件时b文件只能等待,现在a和b可以同时传输
- HTTP2性能瓶颈:多个 HTTP请求复用一个 TCP 连接,一旦发生丢包,一个TCP连接中的HTTP请求都必须等待丢的包被重传
- HTTP3改进:
- HTTP下层的TCP改成UDP,UDP不管顺序,因此不存在HTTP1.1的队头阻塞 和HTTP2的丢包全部重传问题
- QUIC:基于UDP实现类似TCP的可靠性传输,当某个流丢包,只会阻塞这个流,其他流不受影响
- TLS为1.3版本,头部压缩算法从HPack升级成QPack
- HTTPS建立一个连接要6次交互(三次握手,TLS1.3三次握手),QUIC合并成3 次
- HTTP1.1性能瓶颈
- 大文件传输:
- 压缩文件:通常浏览器在发送请求时都会带着“Accept-Encoding”头字段,里面是浏览器支持的压缩格式列表
- 分块传输:分批发给浏览器,浏览器收到后再组装复原。在响应报文里用头字段“Transfer-Encoding: chunked”来表示(“Transfer-Encoding: chunked”和“Content-Length”字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知)
- 每个分块包含分块长度和数据块两个部分
- 分块长度使用 16 进制数字表示,以
\r\n
结尾;数据块紧跟在分块长度后面,也使用\r\n
结尾,但数据不包含\r\n
;终止块是一个常规的分块,表示块的结束。不同之处在于其长度为 0,即0\r\n\r\n
- 范围请求:在一个 Range 首部中,可以一次性请求多个部分,服务器会以 multipart 文件的形式将其返回(类似断点续传)
- 断点传输
- 基本原理
- 对上传的文件进行分割,每次只上传一小片。服务端接收到文件后追加到原来部分,最后合并成完整的文件
- 每次上传文件片前先获取已上传的文件大小,确定本次应切割的位置
- 每次上传完成后更新已上传文件大小的记录
- 标识客户端和服务端的文件,保证不会把A文件的内容追加到B文件上
- 在上一次下载断开的位置开始继续下载——HTTP中,在请求报文头中加入Range段,来表示客户机希望从何处继续下载
- 客户端发请求时对应的是 Range ,服务器端响应时对应的是 Content-Range
- Range用于请求头中,指定第一个字节的位置和最后一个字节的位置
- Content-Range用于响应头中,服务器在 Content-Range 段返回当前发送的范围和文件总大小,响应完成后,返回的响应头为:HTTP/1.1 206 Partial Content
- 需要标识文件唯一性(可能重传时,目标文件不在该url)——Last-Modified 是由服务器往客户端发送的 HTTP 头,用于记录页面最后修改时间
- 基本原理
TCP和UDP
区别:
- TCP面向连接,提供可靠服务(传送数据无差错不丢失),适合文件传输、远程登录等;UDP无连接,不保证可靠交付
- TCP面向字节流,把数据看成一连串无结构的字节流;UDP面向报文,没有拥塞控制,网络拥塞时不会降低客户端的发送速率(适合实时应用,如IP电话,实时视频会议)
- 一条TCP连接只能是点到点;UDP支持一对一,一对多,多对一和多对多的通信
- TCP首部20字节;UDP首部8个字节(源端口号、目标端口号、包长、校验和)
TCP可靠传输:
- 编号:给发送包编号,接收方据此排序,丢弃重复的数据,把有序数据传给应用层
- 校验和:TCP保持首部和数据的检验和(端到端的检验和)
- 流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。接收方会将自己接收缓冲区的大小放入TCP首部的字段,通过ACK通知发送方——TCP利用滑动窗口实现流量控制
- 拥塞控制:网络拥塞减少数据的发送——慢启动策略
- ARQ协议:每发完一个分组就停止发送,等待对方确认,收到确认后再发下一个分组
- 超时重传:每发完一个分组就启动一个定时器,等待目的端的ACK,如果不能及时收到ACK则重传;若尝试几次均失败,TCP会断开连接,发送RST,告知应用程序连接错误
三次握手四次挥手:
三次握手:第一二次握手没有应用层数据,第三次可以携带客户端到服务器的数据
- 开始,客户端和服务端处于CLOSED状态,之后服务端主动监听某个端口,处于LISTEN状态
- 客户端随机初始化序号(client_isn),置于TCP首部的“序号”字段中,设置SYN标志位(SYN报文),把第一个SYN报文发给服务端,发起连接,之后客户端处于SYN-SENT状态
- 服务端收到SYN报文,也随机初始化序号(server_isn),将序号填入TCP“序号”字段,并将client_isn+1填入首部的“应答序号”字段,设置SYN和ACK标志位,发送报文给客户端,之后服务端处于SYN-RECEIVED状态
- 客户端收到报文,向服务端回应最后一个应答报文:设置报文首部ACK标志位,“确认应答号”字段填入server_isn+1,发送报文给服务端,之后客户端处于ESTABLISHED状态
- 服务器收到客户端的应答报文,也进入ESTABLISHED状态
- 客户端主动打开,服务器被动打开
四次挥手:
- 过程:
- 客户端关闭连接,发送设置首部FIN标志位的报文,进入FIN_WAIT_1状态
- 服务端收到后,发送ACK应答报文,进入CLOSED_WAIT状态
- 客户端收到ACK应答报文,进入FIN_WAIT_2状态
- 服务端处理完数据,向客户端发送FIN报文,进入LAST_ACK状态
- 客户端收到FIN报文,回复ACK应答报文,进入TIME_WAIT状态
- 服务器收到ACK应答报文,进入CLOSED状态,服务端关闭连接
- 客户端经过2MSL后,进入CLOSED状态,关闭连接
- 主动关闭连接的一方才有TIME_WAIT状态
- 过程:
三次握手原因:防止历史连接的建立,减少双方不必要的资源开销,并同步初始化序列号
- 两次:无法可靠地同步双方序列号,只有客户端的起始序列号被确认,服务器的序列号得不到确认
- 四次:三次握手就能建立可靠连接
- 四次挥手原因:服务端收到客户端的FIN后,可能还有数据没发完
TIME_WAIT=2MSL:保证上一次连接的报文在网络中消失
- 更短可能会导致具有相同四元组的”旧“数据包被收到
- 更长导致内存资源和端口资源的占用
- MSL:Maximum Segment Lifetime
- 两次:无法可靠地同步双方序列号,只有客户端的起始序列号被确认,服务器的序列号得不到确认
TCP半连接队列和全连接队列
三次握手时,Linux内核会维护两个队列:
- 半连接队列(SYN队列)
- 全连接队列(accepet队列)
服务端收到客户端发起的SYN请求后,内核将该连接存储到半连接队列
服务端收到第三次握手的ACK后,内核将该连接从半连接队列移除,创建新的完全的连接,添加到accept队列
进程调用accept函数取出连接
- TCP拥塞控制:
- 慢启动:由小到大逐渐增加拥塞窗口(发送端在一个RTT内可以最多发送的数据包数)的大小——令cwnd=1,发送方只发送1个数据包,收到确认将cwnd加倍,之后能发送的数据包数量为:2、4、8 ...
- 拥塞避免:当cwnd >= ssthresh(慢开始门限) 时,进入拥塞避免,每个轮次cwnd加1
- 拥塞发生:如果出现超时,令ssthresh=cwnd / 2,重新执行慢开始
- 快重传:接收方收到一个失序的报文段后立刻发出最后一个有序报文段的重复确认,而不是捎带确认。如果发送方连续收到三个重复确认,则立即重传对方尚未收到的报文段,而不等待重传计时器到期
- 快恢复:发送方连续收到三个重复确认时,ssthresh/=2,cwnd=ssthresh,执行拥塞避免
TCP滑动窗口
Sender发送一个数据包后不等待ACK消息返回,直接发送后面的数据包,提高吞吐量
发送方和接收方各有一个窗口,接收方通过Header的窗口字段告诉发送方自己的窗口大小(接收端能接收的最大字节数),发送方据此和其它信息设置自己的窗口大小
image-20220626141343038 发送窗口收到发送窗口内的ACK确认,才移动左边界
接收窗口只有在前面所有段都确认的情况下才移动左边界
粘包、拆包:根源在于TCP面向字节流,消息之间没有消息保护边界
粘包原因:一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送
拆包原因:一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送
解决:
- 发送端给每个数据包的首部添加数据包的长度
- 发送端把每个数据包设置为固定长度,接收端每次读取固定长度的数据,以拆分每个数据包
- 发送端在每个数据包之间设置边界,接收端通过这个特殊符号来拆分
IP地址
- 由四段组成,每个字段一个字节,8位
- 由两部分组成:网络地址和主机地址。网络地址表示属于互联网的哪一个网络,主机地址表示其属于该网络中的哪一台主机
- 分为A、B、C三类及特殊地址D、E
- A类:(1.0.0.0-126.0.0.0),一般用于大型网络。
- B类:(128.0.0.0-191.255.0.0),用于中等规模网络。
- C类:(192.0.0.0-223.255.255.0),用于小型网络。
- D类:多播地址,地址的网络号取值于224~239之间,一般用于多路广播用户
- E类:保留地址。地址的网络号取值于240~255之间
浏览器键入一个网址经历了哪些过程?
整体过程:
解析URL,生成HTTP请求信息
- 查询服务器域名对应的IP地址(DNS解析)
- 获取IP后,HTTP的传输⼯作交给操作系统中的协议栈
- TCP三次握手建立连接
- TCP模块执行连接、收发、断开等操作时,需要委托IP模块将数据封装成网络包发送
- 生成IP头部后,在IP头部前面加上MAC头
经过网卡、交换机和路由器到目的端,然后反向
- DNS解析
- 本地hosts文件查找是否有网址的映射关系,有则直接调用该映射进行访问
- 没有则去本地DNS解析缓存查找
- 没有则首先找本地TCP/IP设置的DNS服务器,如果网址在本地DNS资源范围内则返回解析给主机(解析具有权威性)
- 没有,但该服务器已经存储了网址的映射关系,同样返回这个映射,完成地址解析(解析不具有权威性)
- 如果本地DNS解析失败,没有对应的映射关系:
- 本地DNS服务器开启转发模式,则向上一级请求,若上一级也不能解析就找上上一级,以此类推,最后返回给主机
- 本地DNS服务器不使用转发模式,直接找13组根DNS,根DNS返回负责这个域名的DNS,重复过程
Ping的工作原理
- Ping基于ICMP协议栈,ping某IP地址或域名,检查基本连接是否正常,以及网络延迟情况
- 过程:
- 源主机先构建一个ICMP回送请求消息数据包
- ICMP消息包含多个字段,最重要的是:
- 类型:对于回送请求消息,该字段为8
- 序号:用于区分连续ping的时候,发出的多个数据包
- 每发出一个请求数据包,序号自动加1
- 在报文的数据部分插⼊发送时间,以计算往返时间RTT
- 规定时间内:
- 源主机没有收到ICMP应答包,说明目标主机不可达
- 收到ICMP应答包,则源主机用当前时刻减去该数据包从源主机发出的时刻,得到ICMP数据包时间延迟
Cookie和Session
- Cookie和Session都是用来跟踪浏览器用户身份的会话方式
- Cookie:保存用户信息,不可跨域名。服务器在本地机器上存储的一小段文本,浏览器每次访问网页都会带上cookie。应用场景:
- 保存已经登录过的用户信息,下次访问网站时,页面自动填写一些登录信息
- 网站保持登录(下次访问不需要登录):用户登录的时候,在Cookie保存一个Token,下次登录时服务器根据Token值来查找用户
- Session:通过服务端记录用户的状态。客户端浏览器访问服务器,服务器把客户端信息以某种形式记录在服务器上。应用场景:
- 购物车:添加商品到购物车时,HTTP协议是无状态的,系统不知道是哪个用户操作
- 服务端给特定的用户创建特定的Session,以标识、跟踪这个用户
- Cookie:保存用户信息,不可跨域名。服务器在本地机器上存储的一小段文本,浏览器每次访问网页都会带上cookie。应用场景:
- cookie中的一些敏感信息最好加密,使用的时候发给服务器解密
- 会话cookie:浏览器关闭后失效,存放在内存中;持久cookie:存放在硬盘
- session默认被存储在服务器的一个文件里(不是内存)
- session的运行依赖session id,session id存储在cookie——如果浏览器禁用cookie,session也会失效(但可以通过其它方式实现,如在url中传递session id)——用户验证一般用session,客户端每次请求服务器的时候会发送session_id,服务器根据当前session_id判断相应的用户数据标志,以确定用户是否登录,或具有某种权限
- session真正的失效(即在服务器端被删除)是取决于服务器端设置的session过期时间
- 但由于依赖cookie存储session id,因此可能服务器的session没有过期,但cookie丢失导致session id丢失
- 关闭浏览器只会使存储在客户端浏览器内存中的session cookie失效,不会使服务器端的session对象失效
XSS攻击
- 跨站脚本攻击(Cross Site Scripting),利用站点内的新人用户
- 攻击者在web页面中插入恶意的script代码,用户浏览该页面时嵌入web页面的script代码会执行
- 分类:反射型、存储型、DOM-based型
- 反射性和DOM-baseed型为非持久性XSS攻击
- 存储型为持久性XSS攻击
MySQL
事务四大特性
- 原子性:事务的所有操作要么全部成功,要么全部失败回滚——MySQL的InnoDB引擎是靠undo log(回滚日志)来实现的,undo log能够保证在事务回滚时,撤销所有已经执行成功的SQL
- 一致性:一个事务执行之前和执行之后都必须处于一致性状态。比如a与b账户共有1000块,转账之后无论成功失败,账户总和还是1000——应用层面保证
- 隔离性:多个事务彼此之间隔离、互不干扰——锁机制+MVCC
- 两个事务并发访问同一数据资源的情况主要分为
读-读
、写-写
和读-写
三种;锁机制解决写-写
,MVCC解决读-写
(读操作利用MVCC
,写操作进行加锁) - 锁机制:
- 读锁:又称“共享锁”
- 写锁:又称“排他锁”
- 表级锁:将整个表进行锁定
- 行级锁:将需要操作的相应行进行锁定
- 意向锁:表级锁,一个事务已经对一个表中的某个数据加上了排他锁或共享锁,可以加上意向锁,这样下一个事务来锁表时发现已经存在意向锁了,否则需要遍历查看是否有数据已经被锁住
- 间隙锁:防止产生幻读,加在不存在的空闲空间,可以是两个索引记录之间,也可能是第一个索引记录之前或最后一个索引之后的空间。保证在间隙锁执行的时候,新增的数据会阻塞,保证一个事务中的两次查询获得的记录数是一致的
- Next-Key Lock:行级锁和间隙锁的结合产生的锁。间隙锁不会锁住当前记录,Next-Key Lock会将当前记录也锁住
- MVCC:通过快照读和当前读保证读写隔离
- 两个事务并发访问同一数据资源的情况主要分为
- 持久性:一个事务一旦被提交,对数据库中的数据的改变是永久性的,即使数据库崩溃也可以回滚——InnoDB提供缓冲池(Buffer Pool),包含了磁盘中部分数据页的映射。在对Buffer Pool中的数据进行修改的时候通过redo log记录这次操作,事务提交时对redo log进行刷盘。 宕机时从磁盘中读取redo log进行数据的恢复,保证事务的持久性——redo log先记录日志,再更新Buffer Pool
事务隔离级别
- 脏读、不可重复读、幻读
- 脏读:在一个事务处理过程中,读取另一个未提交的事务的数据
- 不可重复读:一个事务多次查询某行记录却返回不同的数据值——读取的间隔中,另一个事务修改数据并提交(脏读读取未提交的数据,不可重复读读取前一个事务提交的数据)
- 幻读:某个事务在读取某个范围的记录时,另一个事务在该范围内插入新的记录。第一个事务再次读取该范围记录时,会产生幻行(不可重复读的重点是修改,幻读的重点是增加和删除)
- 事务隔离能够解决脏读、不可重复读、幻读
- 四种隔离级别:
- Serializable (串行化):强制事务排序,使之不可能冲突,解决幻读
- Repeatable read (可重复读):默认事务隔离级别,确保同一事务多个实例在并发读取数据时,看到同样的数据行,解决不可重复读
- Read committed (读已提交):一个事务只能看见已经提交事务所做的更改,解决脏读
- Read uncommitted (读未提交):事务可以看到其他未提交事务的执行结果
查看隔离级别:
1
select @@transaction_isolation;
设置隔离级别:
1
set session transaction isolation level read uncommitted;
三大范式
第一范式1NF:数据库表字段是原子的,例如字段
userInfo: 广东省 10086'
必须拆分成userInfo: 广东省
和userTel: 10086
两个字段- 第二范式2NF:满足第一范式,并且:
- 一个表有一个主键
- 非主键列完全依赖于主键,而不能只依赖于主键的一部分
- 例如:关系表
student_course(student_no, student_name, age, course_name, grade, credit)
,主键为(student_no, course_name),学分完全依赖于课程名称,姓名年龄完全依赖学号,不符合第二范式
- 第三范式3NF:满足第二范式,并且:
- 非主键列直接依赖于主键,不存在传递依赖
- 例如,关系表
Student(student_no, student_name, age, academy_id, academy_telephone)
,主键为"学号",学院id依赖于学号,学院地点和学院电话依赖于学院id,存在传递依赖
索引
存储引擎用于提高数据库表访问速度的一种数据结构;查询数据时如果没有索引,会加载所有的数据到内存依次进行检索,而有索引时B+树的高度一般在2-4层,最多只需要读取2-4次磁盘
- 优缺点:
- 优点:
- 加快数据查找的速度
- 排序或者是分组的字段添加索引,可以加快分组和排序的速度
- 加快表与表之间的连接
- 缺点:
- 占用物理空间
- 要动态维护索引,降低表的增删改效率
- 优点:
- 建立索引:经常用于查询的字段、经常用于连接的字段、经常需要排序的字段,需要建立索引(索引已经排好序)
不建索引:where中用不到的字段、表记录较少、经常增删改、区分度不高的字段
数据结构:B+树、哈希表,InnoDB引擎的索引类型包括B+树索引和哈希索引,默认索引类型为B+树索引
B+树索引
B+树是基于B 树和叶子节点顺序访问指针进行实现,具有B树的平衡性,并且通过顺序访问指针来提高区间查询的性能。
节点中的key从左到右递增排列,如果某个指针的左右相邻 key 分别是\(key_i\)和\(key_{i+1}\),则该指针指向节点的所有key大于等于\(key_i\)、小于等于\(key_{i+1}\)
查找时,先在根节点二分查找,找到key所在的指针,递归地在指针所指向的节点查找,直到查找到叶子节点,然后在叶子节点上二分查找,找出key对应的数据项
哈希索引:
- 基于哈希表实现
对于每一行数据,存储引擎会对索引列进行哈希计算得到哈希码(哈希算法尽量保证不同列值的哈希码不同)
哈希码作为哈希表的key,指向数据行的指针作为value
Hash索引和B+树索引的区别
- 哈希索引不支持排序、范围查找、模糊查询、多列索引的最左前缀匹配
- 哈希索引中会存在哈希冲突,因此哈希索引的性能不稳定
B+树比B树适合索引
- B+树的数据存储在叶子结点中,扫库时扫一遍叶子结点即可,而B树分支结点存储数据,扫库时要一次中序遍历按序,因此B+树适合区间查询的情况
- B+树节点只存储索引key值,以页为单位的索引中可以存放更多的节点
- B+树的查询效率更加稳定,每一个数据的查询效率相当
索引分类:
主键索引:索引列中的值是唯一的,不允许有空值;建表时指定了主键,就会自动创建主键索引
唯一索引:索引列中的值是唯一的,但允许为空值——唯一标识数据库表中的每条记录,防止数据重复插入。创建唯一索引的SQL语句如下:
1
ALTER TABLE table_name ADD CONSTRAINT constraint_name UNIQUE KEY(column_1,column_2,...);
组合索引:表中的多个字段组合上创建的索引,使用组合索引时需遵循最左前缀原则
全文索引:只在
MyISAM
引擎上使用,只在CHAR
、VARCHAR
和TEXT
类型字段上使用全文索引
最左匹配原则
如果 SQL 语句中用到组合索引中的最左边的索引,则 SQL 语句可以利用组合索引去匹配。遇到范围查询(
>
、<
、between
、like
)停止匹配,后面的字段不用到索引对
(a,b,c)
建立索引,查询条件使用 a/ab/abc 会走索引,使用 bc 不会走索引对
(a,b,c,d)
建立索引,查询条件为a = 1 and b = 2 and c > 3 and d = 4
,a、b和c三个字段能用到索引,d无法使用索引如下图,对(a, b)建立索引,a在索引树全局有序,b是全局无序,局部有序(a相等时根据b[排序)。直接执行
b = 2
这种查询条件无法使用索引。当执行a = 1 and b = 2
时a和b字段能用到索引。执行a > 1 and b = 2
时,a字段能用到索引,b字段用不到索引,因为在这个范围内b值不是有序的
聚集索引
- 索引结构和数据一起存放的索引(如果为聚集索引,叶子节点直接存放数据,否则存放指向数据的指针)
- 聚集索引的叶子节点是整张表的行记录。InnoDB 主键使用聚集索引
- 如果表中没有指定主键,则选择表中的第一个不允许为
NULL
的唯一索引。如果没有主键也没有唯一索引,InnoDB
内部生成一个隐藏的主键作为聚集索引,隐藏主键长6个字节,值随着数据的插入自增 - 以主键作为 B+ 树索引的键值而构建的 B+ 树索引,称为聚集索引;以主键以外的列值作为键值构建的 B+ 树索引,称为非聚集索引;在InnoDB中,主键索引即聚集索引
覆盖索引
select
的数据列只从索引中就能够取得,不需要回表进行二次查询——查询列要被该表所使用的索引覆盖(回表:对于非聚集索引,需要遍历两遍索引树,先通过非聚集索引定位到主键值,再通过聚集索引定位到行记录)覆盖索引要存储索引列的值,而哈希索引、全文索引不存储索引列的值,所以MySQL使用b+树索引做覆盖索引
对于使用了覆盖索引的查询,在查询前面使用
explain
,输出的extra列会显示为using index
比如
user_like
用户点赞表,组合索引为(user_id, blog_id)
,user_id
和blog_id
都不为null
。1
explain select blog_id from user_like where user_id = 13;
- 查询的列被索引覆盖,where筛选条件符合最左前缀原则,通过索引查找就能直接找到符合条件的数据,不需要回表查询数据
1
explain select user_id from user_like where blog_id = 1;
- 查询的列被索引覆盖,where筛选条件不符合最左前缀原则,无法通过索引查找找到符合条件的数据,但可以通过索引扫描找到符合条件的数据,不需要回表查询数据
前缀索引
在很长的字符列上创建索引,会造成索引特别大。前缀索引是指对文本或者字符串的前几个字符建立索引,关键在于选择足够长的前缀
1
2// email列创建前缀索引
ALTER TABLE table_name ADD KEY(column_name(prefix_length));
索引设计原则
- 索引列的区分度要高
- 尽量使用短索引,对于较长的字符串进行索引应该指定一个较短的前缀长度,以减少磁盘I/O,提高索引缓存可以容纳的键值数量
- 利用最左前缀原则
索引失效
- 对于组合索引,不使用组合索引最左边的字段,则不会使用索引
- 以%开头的like查询(非%开头的like查询如 abc%会使用索引)
- 判断索引列是否不等于某个值
- 查询条件使用or连接
- 对索引列进行运算
存储引擎
- MySQL常用的四种存储引擎: MyISAM、InnoDB、MEMORY、ARCHIVE。默认存储引擎为
InnoDB
InnoDB存储引擎
- 默认的事务型存储引擎,基于聚集索引建立
- 同一个树节点中同时存放索引和数据;非主键索引的树节点data为主键的值
优点缺点:
支持事务、具有崩溃修复能力;引入了行级锁和外键约束
- 占用的数据空间较大
场景:需要事务支持,有较高的并发读写频率
MyISAM存储引擎
- 数据以紧密格式存储。只读数据、小表、可以容忍修复操作,使用MyISAM引擎。MyISAM将表存储在两个文件:数据文件
.MYD
、索引文件.MYI
。 - 使用B+Tree作为索引结构,叶节点的data存放数据记录的地址
- 优点缺点:
- 访问速度快
- MyISAM不支持事务和行级锁,不支持崩溃后的安全恢复,不支持外键
- 场景:对事务完整性没有要求;数据只读
MEMORY存储引擎
数据放在内存,使用哈希索引,将键的哈希和指向数据行的指针保存在哈希索引中
- 优点缺点:
- 访问速度较快
- 哈希索引数据不是按照索引值顺序存储,无法用于排序;不支持部分索引匹配查找,因为哈希索引使用索引列的全部内容计算哈希值;只支持等值比较,不支持范围查询;哈希冲突时,引擎遍历链表中所有的行指针,逐行比较直到找到符合条件的行
ARCHIVE存储引擎
- 适合存储大量独立的、作为历史记录的数据。提供压缩功能,有高效的插入速度,不支持索引,查询性能差
MyISAM和InnoDB的区别?
是否支持行级锁:
MyISAM
只有表级锁,InnoDB
支持行级锁和表级锁,默认为行级锁是否支持事务和崩溃后的安全恢复:
MyISAM
不提供事务支持。InnoDB
提供事务支持,具有事务、回滚和崩溃修复能力是否支持外键:
MyISAM
不支持,InnoDB
支持。是否支持MVCC:
MyISAM
不支持,InnoDB
支持。对高并发事务,MVCC比单纯的加锁更高效。是否支持聚集索引:
MyISAM
不支持聚集索引,InnoDB
支持聚集索引
MVCC
- MVCC( Multiversion concurrency control ) :同一份数据保留多版本的一种方式,实现并发控制。查询的时候,通过read view和版本链找到对应版本的数据
- 对于高并发场景,MVCC比行级锁更有效
实现原理
版本链:
通过三个隐藏字段实现
DB_TRX_ID :当前事务id;通过id大小判断事务的时间顺序
DB_ROLL_PRT :回滚指针;指向当前行记录的上一个版本
DB_ROLL_ID :主键;如果表没有主键,InnoDB会自动生成自增的主键
使用事务更新行记录时会生成版本链:
用排他锁锁住该行
将该行原本的值拷贝到 undo log,作为旧版本用于回滚
修改当前行,生成一个新版本,更新事务id,回滚指针指向旧版本的记录
read view:对数据在每个时刻的状态拍照记录,获取某时刻的数据就是快照恢复
read view 内部维护一个活跃事务链表,表示生成 read view 时还活跃的事务——链表包含在创建 read view 之前未commit的事务,不包含创建 read view 之后commit的事务
不同隔离级别创建read view的时间不同
- 可重复读:一个事务范围内,第一次select时更新这个read_view,以后不再更新,后续的select复用之前的read_view。保证事务范围内每次读取的内容一样
- 读已提交:每次执行select创建新的read_view,保证读到其他事务已经提交的修改
read view 的记录筛选方式:(DATA_TRX_ID 表示每行的最新事务ID; up_limit_id 表示当前快照最先开始的事务;low_limit_id 表示当前快照最后一个事务)
- 如果 DATA_TRX_ID < up_limit_id :创建 read view 时,修改该数据行的事务已提交,该 版本的数据行可被当前事务读取到
- 如果 DATA_TRX_ID >= low_limit_id :当前版本数据行的事务是在创建 read view 之后生成的,该版本的数据行不可以被当前事务访问,需要通过版本链找到上一个版本,重新判断该版本的记录对当前事务的可见性
- 如果 up_limit_id <= DATA_TRX_ID < low_limit_i :
- 在活跃事务链表中查找ID为 DATA_TRX_ID 的事务
- 如果存在,所以该记录不可见(活跃事务链表中的事务是未提交的)。通过版本链找上一个版本,重新判断该版本的可见性
- 如果不存在,说明事务trx_id 已经提交,这行记录是可见的
InnoDB 的 MVCC 通过 read view 和版本链实现,版本链保存有历史版本记录,通过 read view 判断当前版本的数据是否可见,如果不可见,根据版本链找上一个版本,继续判断
快照读和当前读
- 表记录有两种读取方式
- 快照读:读取快照版本
- 普通的
SELECT
就是快照读,通过mvcc进行并发控制的,不用加锁 - InnoDB通过
mvcc
机制避免了幻读
- 普通的
- 当前读:读取最新版本。
UPDATE、DELETE、INSERT、SELECT … LOCK IN SHARE MODE、SELECT … FOR UPDATE
是当前读mvcc
机制无法避免”当前读“出现的幻读现象——当前读每次读取的都是最新数据,如果两次查询中间有其它事务插入数据,产生幻读
- 快照读:读取快照版本
MySQL如何避免幻读
- 快照读情况下,MySQL通过
mvcc
来避免幻读。 当前读情况下,MySQL通过
next-key
(加行锁和间隙锁)来避免幻读——行锁是加在索引上的锁,间隙锁是加在索引之间的Serializable
隔离级别也可以避免幻读,会锁住整张表,并发性极低,一般不使用
共享锁和排他锁
- SELECT 的读取锁定分为:共享锁和排他锁。
1 | select * from table where id<6 lock in share mode;--共享锁 |
LOCK IN SHARE MODE
:多个事务同时更新同一个表单时,容易造成死锁- 排他锁:申请排他锁的前提是,没有线程对该结果集的任何行数据使用排它锁或者共享锁。进行事务操作时,MySQL会对查询结果集的每行数据添加排它锁,其他线程只能读这些数据,直到该语句的事务被
commit
语句或rollback
语句结束为止for update
仅适用于innodb,在事务范围内才生效- 根据主键进行查询,查询条件为
like
或者不等于时,主键字段产生表锁 - 根据非索引字段进行查询,会产生表锁
MySQL日志:bin log/redo log/undo log
- MySQL日志:包括查询日志、慢查询日志、事务日志、错误日志、二进制日志等
bin log
(二进制日志)、redo log
(重做日志)和、undo log
(回滚日志)。- bin log
- MySQL数据库级别的文件,记录对MySQL数据库执行修改的操作,不记录select和show
- 用于恢复数据库、同步数据库
- redo log
- Innodb引擎级别的文件,记录innodb存储引擎的事务日志,不管事务是否提交
- 数据库发生故障时,InnoDB存储引擎使用
redo log
恢复到发生故障前的时刻 - 参数
innodb_flush_log_at_tx_commit
设置为1,commit时会将redo log
同步写到磁盘
- undo log
- 进行数据修改时还会记录
undo log
,用于数据的撤回操作——保留了修改前的内容,如果事务执行失败或者调用rollback,InnoDB引擎会根据undo log中的记录,将数据回滚到之前的样子 - 可以根据
undo log
回溯到某个特定的版本的数据,实现MVCC
- 进行数据修改时还会记录
大表优化
- 单表记录数过大时,数据库的性能会明显下降,常见的优化措施:
- 限定数据的范围。比如:查询历史信息时,限制时间范围为一个月
- 读写分离。数据库拆分,主库负责写,从库负责读;
- 通过分库分表的方式进行优化——垂直拆分和水平拆分
MySQL 执行计划
- 通过 explain 命令获取 select 语句的执行计划,了解 select 语句以下信息:(
explain select ...
)- 表的加载顺序
- sql 的查询类型
- 可能用到哪些索引,实际上用到哪些索引
- 读取的行数
MySQL架构
- MySQL主要分为 Server 层和存储引擎层:
- Server 层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能在这一层实现,比如存储过程、触发器、视图,函数等(日志模块 bin log 也在 Server 层)
- 存储引擎:主要负责数据的存储和读取。Server 层通过 api 与存储引擎通信
- Server 层基本组件
- 连接器:客户端连接 MySQL 时,server层对其进行身份认证和权限校验
- 查询缓存:执行查询语句时,先查询缓存,校验该 sql 是否执行过,如果缓存中有该 sql 结果则直接返回给客户端,如果没有则执行后续的操作
- 分析器:没有命中缓存的话,SQL 语句就会经过分析器(词法分析和语法分析,看 SQL 语句要做什么,检查 SQL 语句语法是否正确)
- 优化器:优化器对查询进行优化,包括重写查询、决定表的读写顺序、选择合适的索引等,生成执行计划
- 执行器:执行前会校验用户有没有执行权限,没有权限会返回错误信息,有权限会根据执行计划调用引擎的接口,返回结果
分库分表
切分的目的在于减少数据库的负担,缩短查询的时间
垂直划分:根据业务进行划分。通过降低单库的大小来提高性能,但并没有解决高数据量带来的性能损耗
- 行记录变小,数据页可以存放更多记录
- 主键出现冗余,需要管理冗余列; 会引起表连接JOIN操作;依然存在单表数据量过大的问题
水平划分:根据一定规则,例如时间或id序列值等进行数据的拆分
- 单库(表)的数据量得以减少;切分出的表结构相同,程序改动较少
- 分片事务一致性难以解决,跨节点join性能差,逻辑复杂,数据分片在扩容时需要迁移
分区表
分区表是一个独立的逻辑表,底层由多个物理子表组成。当要查询的数据分布在某一个分区时,查询引擎只去某一个分区查询,而不是遍历整个表。如果需要删除某一个分区的数据,只需要删除对应的分区即可
分区方法:
按照范围分区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19CREATE TABLE test_range_partition(
id INT auto_increment,
createdate DATETIME,
primary key (id,createdate)
)
PARTITION BY RANGE (TO_DAYS(createdate) ) (
PARTITION p201801 VALUES LESS THAN ( TO_DAYS('20180201') ),
PARTITION p201802 VALUES LESS THAN ( TO_DAYS('20180301') ),
PARTITION p201803 VALUES LESS THAN ( TO_DAYS('20180401') ),
PARTITION p201804 VALUES LESS THAN ( TO_DAYS('20180501') ),
PARTITION p201805 VALUES LESS THAN ( TO_DAYS('20180601') ),
PARTITION p201806 VALUES LESS THAN ( TO_DAYS('20180701') ),
PARTITION p201807 VALUES LESS THAN ( TO_DAYS('20180801') ),
PARTITION p201808 VALUES LESS THAN ( TO_DAYS('20180901') ),
PARTITION p201809 VALUES LESS THAN ( TO_DAYS('20181001') ),
PARTITION p201810 VALUES LESS THAN ( TO_DAYS('20181101') ),
PARTITION p201811 VALUES LESS THAN ( TO_DAYS('20181201') ),
PARTITION p201812 VALUES LESS THAN ( TO_DAYS('20190101') )
);list分区。分区字段必须是已知的,如果插入的字段不在分区时枚举值中,将无法插入
1
2
3
4
5
6
7
8
9
10create table test_list_partiotion(
id int auto_increment,
data_type tinyint,
primary key(id,data_type)
)partition by list(data_type)
(
partition p0 values in (0,1,2,3,4,5,6),
partition p1 values in (7,8,9,10,11,12),
partition p2 values in (13,14,15,16,17)
);hash分区,将数据均匀地分布到预先定义的分区中
1
2
3
4
5create table test_hash_partiotion(
id int auto_increment,
create_date datetime,
primary key(id,create_date)
)partition by hash(year(create_date)) partitions 10;
潜在问题:
- 打开和锁住所有底层表的成本可能很高。当查询访问分区表时,MySQL需要打开并锁住所有的底层表——可以通过批量操作降低此类开销,如批量插入、LOAD DATA INFILE、一次删除多行数据
- 所有分区必须使用相同的存储引擎
查询语句执行流程?
- 权限校验、查询缓存、分析器、优化器、权限校验、执行器、引擎
1 | update user set name = '大彬' where id = 1; |
- 流程:
- 检查权限,没有权限则返回错误
- 查询缓存,缓存命中则直接返回
- 词法分析和语法分析。提取表名、查询条件,检查语法是否有错误;
- 两种执行方案:先查 id > 1 还是 name = '大彬' ,优化器根据自己的优化算法选择执行效率最好的方案
- 校验权限,有权限就调用数据库引擎接口,返回引擎的执行结果
更新语句执行过程?
- 分析器、权限校验、执行器、引擎、
redo log
(prepare
状态)、bin log
、redo log
(commit
状态)
1 | update user set name = '大彬' where id = 1; |
- 流程:
- 先查询 id 为 1 的记录,有缓存会使用缓存
- 拿到查询结果,name 更新为大彬,调用引擎接口,写入更新数据,innodb 引擎将数据保存在内存中,记录
redo log
,此时redo log
进入prepare
状态 - 执行器收到通知后记录
bin log
,调用引擎接口,提交redo log
为commit
状态 - 更新完成
- 记录完
redo log
,不直接提交而是先进入prepare
状态- 假设写
redo log
后直接提交,然后写bin log
。此时如果写完redo log
,机器挂了,bin log
日志没有被写入,重启后机器会通过redo log
恢复数据。但后续进行数据库备份时,bin log
没有这个记录,会丢失这条数据——主从同步也会丢失这一条数据
- 假设写
exist和in的区别
略
truncate、delete与drop区别?
- 相同
truncate
、不带where
子句的delete
、drop
都会删除表内的数据drop
、truncate
都是DDL
(数据定义语言),执行后自动提交
- 不同
- truncate 和 delete 只删除数据不删除表;drop 删除表的结构、被依赖的约束、触发器、索引
- 执行速度:drop > truncate > delete
having和where区别
where
子句作用于表和视图,having
作用于组where
在数据分组前过滤,having
在数据分组后过滤
MySQL主从同步
- 数据从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(
master
),其余的服务器充当从服务器(slave
) - 复制是异步的,从服务器不需要一直连接着主服务器
- 好处
- 读写分离
- 在主服务器上生成实时数据,在从服务器上分析数据
- 数据备份,保证数据安全
乐观锁和悲观锁
数据库的并发控制:确保多个事务同时存取数据库中同一数据时,不破坏事务的隔离性和统一性、数据库的统一性
- 悲观锁:假定发生并发冲突,查询完数据时把事务锁,直到提交事务——使用数据库中的锁机制实现
乐观锁:假设不会发生并发冲突,只在提交操作时检查是否数据是否被修改。给表增加
version
字段,在修改提交前检查version
与原来取到的version
是否相等,若相等,表示数据没有被修改,可以更新,否则不能更新——使用版本号或CAS算法实现