leetcode hot100链表最后一题:
设计一个LRU缓存:要实现增删改查的全部O(1)时间复杂度,并且对于与数据结构实现LRU的业务目标。对于查询来说map可以实现O(1)的查询操作,所以map存储<key,index>的结构方便定位链表节点所在位置。
而增删改为了方便操作,应当使用双向链表的结构进行处理,其实说起来比较简单,但实际需要考虑的细节部分还有待加强,后面还得练练:
/*defination of struct node*/
#include<iostream>
#include <map>
using namespace std;
struct Node
{
int key;
int val;
Node* next;
Node* pre;
Node(int _key,int _val){
key = _key;
val = _val;
next = nullptr;
pre = nullptr;
}
};
class LRUCache {
Node* v_head = new Node(-1,-1);
Node* v_tail = new Node(-1,-1);
Node* head;
Node* tail;
int size = 0;
int capacity;
//哈希表实现查找的O(1)
//链表实现增删改的O(1)
//查找key存储在哪
map<int,Node*> maplist;
public:
LRUCache(int capacity) {
v_head->next = v_tail;
v_head->pre = nullptr;
v_tail->next = nullptr;
v_tail->pre = v_head;
this->capacity = capacity;
}
void delete_node(Node* mid_node)
{
maplist.erase(mid_node->key);
Node* right = mid_node->next;
Node* left = mid_node->pre;
mid_node->pre = nullptr;
mid_node->next = nullptr;
left->next = right;
right->pre = left;
delete(mid_node);
size-=1;
}
void insert_node(int key,int value,bool swap)
{
//首先计算容量还够不够
this->size += 1;
Node* new_node = new Node(key,value);
new_node->next = v_head->next;
new_node->pre = v_head;
v_head->next->pre = new_node;
v_head->next = new_node;
maplist[key] = new_node;
if(!swap && this->size > this->capacity)
{
delete_node(v_tail->pre);
}
}
int get(int key) {
if(maplist.find(key) == maplist.end())return -1;
else{
Node* ans = maplist[key];
int ans_num = ans->val;
delete_node(ans);
//这个时候容量超过了,也不能删除
insert_node(key,ans_num,true);
return ans_num;
}
}
void put(int key, int value) {
if(maplist.find(key) != maplist.end())
{
//说明了此时在链表里面有该Key值,直接进行更改即可
delete_node(maplist[key]);
insert_node(key,value, false);
}else{
//此时链表里面不存在key值,在最前面添加该节点
insert_node(key,value, false);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
InnoDB的索引模型: B+ 平衡多枝树的意思:
主键索引:主键索引的叶子节点存储的是整行的数据,因此也被叫做聚簇索引。
非主键索引:非主键索引存储的是主键值,也就是说使用非主键索引之后,还会再次去主键索引里面查,因此也被成为二级索引。再次回的过程 称之为回表。
索引的维护:通过主键映射到了连续页上,如果插入中间叶子节点的话,会导致存不下需要一个新页,后面的数据也要跟着移动。当然当某些行被删掉,此时利用率不高则会进行页的合并。
自增主键:每次都是追加写入,不涉及页的分裂。
有没有什么场景适合用业务字段直接做主键的呢?还是有的。比如,有些业务的场景需求是这样的:
-
只有一个索引;(不占空间)
-
该索引必须是唯一索引。(这是主键的要求)
你一定看出来了,这就是典型的 KV 场景。
如何避免二级索引回表?
覆盖索引:
比如ID是主键,其存储信息为ID,name,age,phone等等,下面两句SQL:
1. SELECT * FROM table where age > 7;
2. SELECT ID FROM table where age > 7;
显然,使用了age的索引,但是1由于需要知道其他信息,所以需要回表,而2覆盖了我的查询需求。
联合索引:
将两个字段进行联合索引,按照字段值的全排列(过滤掉不存在组合)进行hash之后放入B+树,支持最左匹配原则。
索引下推:
举例说明吧,例如索引查询 name = 张%,age = 30
在老版本的MySQL中直接去全扫描满足张姓的人,而在新版本中,可以先根据后面的索引值过滤掉一部分,然后再去回表。
数据库锁:
全局锁: 对于整个数据库进行加锁,典型场景做全库逻辑备份。整个数据库处于只读状态。
- 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
- 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。
InnoDB具备MVCC功能存储了时间戳,因此可以不停摆,但是并不是每一个数据库都用了InnoDB.
表级锁:
行锁:在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
这样不是一次全部拿走所有锁的,执行一行拿一个,则会导致死锁。
那么就需要解决:
死锁和死锁检测:
两个策略:
1. 等待释放,第一个被锁的释放,但是等待时间有要求。太长业务不能等得起,太短导致锁还没获取到呢,就没了。
2.死锁检测:
每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。因此,你就会看到 CPU 利用率很高,但是每秒却执行不了几个事务。
1. 一种头痛医头的方法,就是如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉
2. 控制并发度
3.减少资源的占有
数据库快照:
回滚日志:(undo log)MVCC中特有,每个数据的不同版本就是undo log