八股打卡一
页面置换算法介绍
FIFO
先存进来的页面先被置换出去
LRU
最久未使用的页面先被置换出去
- 代码方面采用哈希表和双向链表实现,双向链表的节点存储key,value以及头节点和尾部节点,哈希表存储的键值对为<key, 对应链表节点>
- 有参构造:传入容量参数,建立头尾伪节点
- int get(int key):如果存在,返回对应的value,并将对应节点移动至双向链表头部;否则返回-1
- void put(int key, int value):map中查询是否存在该节点,若不存在,新建节点放入链表的头部,新建键值对放入map中,判断是否超出map容量,若超出则删除链表尾部的节点及在map中对应的键值对;若map中存在该节点,修改该节点的value值,移动到链表的头部。
class LRUCache {
class DLinkNode{
int key;
int value;
DLinkNode prev;
DLinkNode next;
public DLinkNode(){}
public DLinkNode(int _key, int _value){
this.key = _key;
this.value = _value;
}
}
private Map<Integer, DLinkNode> cache = new HashMap<>();
private int size;
private int capacity;
private DLinkNode head, tail;
public LRUCache(int capacity) {
this.size = 0;
this.capacity = capacity;
head = new DLinkNode(); //伪头部
tail = new DLinkNode(); //伪尾部
head.next = tail;
tail.prev = head;
}
public int get(int key) {
DLinkNode node = cache.get(key);
if(node == null){
return -1;
}
//key 存在,则放入链表头部
moveToHead(node);
return node.value;
}
public void put(int key, int value) {
DLinkNode node = cache.get(key);
if(node == null){
DLinkNode newNode = new DLinkNode(key, value);
cache.put(key, newNode);
addToHead(newNode);
++size;
if(size > capacity){
DLinkNode tail = removeTail();
cache.remove(tail.key);
--size;
}
} else {
node.value = value;
moveToHead(node);
}
}
private void moveToHead(DLinkNode node){
moveNode(node);
addToHead(node);
}
private void moveNode(DLinkNode node){
node.prev.next = node.next;
node.next.prev = node.prev;
}
private void addToHead(DLinkNode node){
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
}
private DLinkNode removeTail(){
DLinkNode node = tail.prev;
moveNode(node);
return node;
}
}
Clock算法
时钟算法采用一个循环链表来存放页面,使用一个指针来遍历链表,若指针指向的当前页面访问位为0,表示该页面最久未访问,将该页面置换出去,设置访问位为1;若当前页面访问位为1,说明最近访问过,将它的访问位置为1,继续遍历,直到找到下一个访问位为0的页面将它置换。
进程的同步与互斥
什么是进程的同步与互斥
- 进程同步:多个并发执行的进程协调和管理它们之间的执行顺序,使得进程按照一定的顺序或时间间隔执行。
- 进程互斥:对于共享资源,一个时刻只能由一个进程访问。
如何实现
- 信号量+PV操作:信号量sem是资源计数器,当某个进程访问共享资源时进行P操作,访问完毕进行V操作。P操作将sem减一,若sem < 0则其他进程处于阻塞等待状态,若sem >=0则能够正常访问;V操作将sem加一,若sem <=0 那么就唤醒一个等待的进程,若sem > 0那么正常执行。
- 临界区:将可能发生互斥问题的代码段称为临界区。进程在访问临界区前需要获得锁,访问完毕后释放锁,保证了同一时间只有一个进程能够进入临界区。
- 互斥锁:是一种锁机制,对于共享资源,它们拥有互斥锁,进程必须获得互斥锁才能够访问共享资源,访问完毕释放锁。
- 条件变量:用于进程间传递信息,以便于在特定条件下唤醒或等待。常与互斥锁一起使用,确保在正确的时刻唤醒或等待。
中断和异常
中断与异常的区别
中断和异常是两种不同的事件,它们都会导致cpu暂停当前的操作,转而去执行一段特定的处理程序。
- 中断是由外部设备或其他处理器产生的。中断是异步的,可以在任何时段发生,与当前执行的指令无关。例如键盘输入、鼠标移动、网络数据到达等。
- 异常是由cpu内部产生的。异常是同步的,它们发生在cpu执行某些指令时,与当前执行的指令有关。例如:除法操作的除数为0、访问非法内存地址、执行非法指令等。
- 中断是可屏蔽或禁止的。cpu可以通过设置标志位或寄存器来忽略或者延迟响应中断信号。
- 异常是不可屏蔽或禁止的。一旦发生异常,cpu必须立即进行处理。
中断的作用
cpu在执行指令时,收到中断信号转而去执行一段预先定义好的代码,执行完毕返回原指令流继续执行,称为中断机制。
- 外设异步通知cpu:外设发生什么情况完成什么任务发送什么消息给cpu都可以通过中断告知cpu。
- cpu间通信:在SMP系统中,一个cpu向另一个cpu发送中断,会产生IPI(处理器间中断)。
- 处理cpu异常:当cpu产生异常时会发送一个中断信号给自己来处理异常。
- 实现系统调用:早期的系统调用是通过中断指令来完成的。
中断的产生
- 外设:外部设备产生的中断信号是异步的,一般也叫硬件中断。
- cpu:IPI也是异步的,硬件中断。
- cpu异常:cpu执行指令产生异常会发送中断信号来处理异常,这种中断信号是同步的,称为软件中断。
cpu异常根据是否需要修复和是否可修复分为三类:
trap陷阱:不需要修复,中断处理完成直接执行下一条指令
fault故障:需要修复也可能修复,中断处理完成重新执行上一条指令
abort终止:需要修复但无法修复,中断处理完成进程或内核会奔溃 - 中断指令:直接用cpu指令产生中断信号,软中断
几种典型的锁
- 互斥锁:实现对共享资源的互斥访问。
- 自旋锁:基于忙等待的锁,线程在尝试获取锁时会不断轮询,直到锁被释放。
- 读写锁:多个线程可以读取共享资源,只有一个线程能够修改共享资源。
- 悲观锁:认为多个线程同时修改共享资源的概率大,对共享资源访问上锁。
- 乐观锁:不管,当产生多个线程同时修改的情况再放弃本次操作。
线程同步的方式
- 互斥锁:线程同步最常用的方式。同一时刻只有一个线程能够访问共享资源。
- 读写锁:多个线程读取共享资源,一个线程修改共享资源。
- 条件变量:用于线程间通信,当一个线程等待条件满足时,其他线程能够发出信号通知等待线程,常与互斥锁共同使用。
- 信号量:控制多个线程对于共享资源的访问。
一条SQL查询语句的执行
- 连接器:连接器与客户端进行连接、获取权限、维护和管理连接;
- 查询缓存:MySQL拿到查询语句后查看查询缓存中是否有该条语句和其返回结果的缓存,有则返回,没有则下一步;
- 分析器:SQL是由多个字符串和空格组成的,分析器分析每个字符串的含义(语法分析);
- 优化器:当表中有多个索引,优化器决定使用哪个索引;当查询设计多表关联,优化器决定连接顺序;
- 执行器:通过分析器和优化器后,执行器执行SQL语句。
事务的四大特性
ACID
- 原子性(Atomicity):事务是不可分割的最小工作单元。一个事务对应一个完整的业务,事务的所有操作,要么全部完成,要不全部不完成,不存在中间状态。若事务在执行过程中出错则回滚到原来的状态。
- 一致性(Consistency):事务执行之前和事务执行之后都必须处于一致性状态。如AB两人的账户共1000元,相互转账后总和也应该是1000元。
- 隔离性(Isolation):允许多个并发事务同时对数据进行读取和修改,执行互不干扰,防止多个并发事务执行由于交叉执行造成数据不一致的情况。与隔离级别有关,事务只能读取已提交的修改。
- 持久性(Durability):一个事务一旦被提交,对于数据的修改是永久性的。
索引的种类
按照数据结构划分
- B+树索引:所有数据存储在叶子节点,复杂度为O(logn),适合范围查询。
- 哈希索引:使用等值查询,效率高。
- 全文索引:MyISAM和InnoDB都支持全文索引,一般在文本类型如char,text,varchar类型上创建全文索引。
- R-Tree索引:为GIS数据创建空间索引。
按照物理存储划分
- 聚集索引:数据存储和索引一起存放,叶子节点会存储一条记录,找到索引就找到了数据。
- 非聚集索引:数据存储和索引不在一起存放,叶子节点存储的是数据行地址。
按照逻辑划分
- 主键索引:特殊的唯一索引,不允许为空值。
- 普通索引:MySQL的基本索引类型,允许为空值和重复值。
- 联合索引:多个字段创建的索引,遵循最左前缀原则。
- 唯一索引:索引列中值唯一,允许为空值。
- 空间索引:MySQL5.7后支持空间索引,遵循OpenGIS几何数据模型规则。
B+树索引的优势
- 单点查询:在进行单点查询时,B树最快的时间代价时O(1), 平均查询时间要比B+树快一些。但是由于B树每个节点都存放记录和索引,查询范围波动比较大,有时候在非叶子节点就访问到了索引,有时候得叶子节点才能访问到索引。B+树的非叶子节点存储的是索引,叶子节点存储的是数据。在数据量相同的情况下,B+树能顾存储更多的索引,访问底层节点数据的磁盘IO操作会更少。
- 插入删除的效率:B+树删除一个节点时,只需要删除一个叶子节点,删除速度快。插入一个节点时,需要增加一个叶子节点(可能导致节点分裂,但是只会涉及到树中的某一条路径)。而B树在删除节点时操作复杂,可能涉及到树的变形。
- 范围查询:B+树的叶子节点数据用链表连接起来,而B树没有,查询效率上B+树要优于B树。对于大量范围查询的操作,选择B+树,如数据库。对于大量单点查询的操作,可以考虑B树,如noSQL的MongoDB。
什么时候需要创建索引
- 表的主关键字:自动创建唯一索引
- 直接条件查询的字段:where条件查询常用字段
- 查询中与其他表关联的字段:如某个字段建立外键关系
- 查询中排序字段:对排序字段添加索引提高排序速度
- 唯一性约束列:某列存在唯一性约束,该列的值唯一,创建唯一索引
- 大表中的关键字段:大表中查询效率较低时,可以对关键字段创建索引
什么时候不需要创建索引
- 小表:小表创建索引会增加额外的开销
- 频繁的插入更新删除操作:索引开销会随着插入更新删除操作次数的增加而增大,频繁被小改修改的表创建索引会损害性能。
- 数据重复分布均匀的字段:创建索引不会有太大的性能提升。
- 很少被查询的字段:为该列创建索引没有明显性能提升。
- 查询结果返回行数较少的字段:查询结果集很小,使用索引没有太大的性能提升。
MVCC机制
MVCC(Multi-Version Concurrency Control)多版本并发控制,用于管理多个并发事务同时对数据读取和修改,而不会导致数据不一致的冲突。核心思想是每个事务在数据库中看到的数据是事务开始的一个快照,并非实际的最新版本,这使得多个事务可以并发执行,互不干扰。
事务特性中的隔离性就是通过锁和MVCC机制来实现的。
具体实现
每个Undo Log日志都有一个roll_pointer(回滚指针)指向上一个版本的Undo Log。这样形成了一个版本链,最新的版本日志会放在链的头部。
在执行select语句时,数据库创建一个ReadView,它包含版本链的统计信息,
- m_ids:记录当前活跃的事务id(未提交)
- min_trx_id:记录版本链尾的id
- max_trx_id:下一个将要分配的事务id(链头id+1)
- creator_trx_id:创建ReadView的事务的id查询规则
该版本是否为当前事务所创建(读取自己的修改数据),是返回,否进一步判断;
该版本的事务id小于min_trx_id(ReadView创建之前,数据已经提交),可以直接访问;
该版本的事务id大于max_trx_id(ReadView创建之后,该版本才开启),不可访问;
该版本的事务id在,min_trx_id, max_trx_id]之间,判断当前版本的事务id是否在m_ids中,在则不可访问,不在则可以访问。