1,myisam 和 innodb
什么是储存引擎?
数据库的储存引擎决定了表在计算机中的储存方式,不同的储存引擎有不同的储存机制,索引技巧等
储存引擎区别?
初步区分的话是这样的,
myisam表锁,不支持事物
innodb行锁,支持事物,innodb支持外键
补充说明:
myisam建表之后会有三个文件(一个是frm表结构文件,一个是myd数据文件,一个是myi索引文件),myisam索引结构文件与数据文件是分开的,另外它的索引是非聚簇(也就是二级索引)所以说myisam的索引都需要回一次表.
另外说明下,myisam索引文件存的是磁盘地址,过程是一次索引查询之后拿到磁盘地址(指针)。
innodb建表之后会有两个文件(一个是表结构文件frm,一个是索引与数据文件ibd)
2,索引
什么是索引
索引是数据库中对一列或者多列值进行排序的一种数据结构,使用索引能快速的访问到想要的数据。
索引都有哪些(mysql)
mysql索引大概有5中
主键索引
唯一索引
联合索引
全文索引
普通索引
mysql索引数据结构演化过程
二叉树-》红黑树-》btree-》b+tree
btree 和 b+tree的区别:
1,btree的关键字和记录是放在一起的,叶子接点可以看做外部节点,不包含任何信息;b+tree的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。
2,btree中,越靠近跟节点的记录查找时间越快,只要找到关键字即可确定记录的存在;而b+tree中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。
联合索引遵循最左原则
举例:
一个表中有 id A B C D 5个字段。
现在建立联合索引 A B C
能用到ABC的情况:
select * from 表 A=? B=?C=?
select * from 表 A=? B=?C>?
select * from 表 A=? B=?C<?
select * from 表 B=? A=?C<?-查询优化器
能用到AB情况:
select * from 表 A=? B>?C=?
select * from 表 A=? B<?C>?
select * from 表 A=? B like ?C<?
select * from 表 A=? B between ?C<?
能用到A情况 与上面AB类似
3,回表
什么是回表?
所谓回表,是指mysql查询数据,拿到相关数据的主键(地址(指针))然后再进行一次查询查到相关具体数据,简而言之若想拿到具体信息就需要俩次查询。
聚簇索引&非聚簇索引
mysql底层数据储存用的是b+tree数据结构;
聚簇索引:叶子接点储存的是数据本身,所以聚簇索引不需要回表,一次就能查到整个真实数据。
非聚簇索引:叶子接点储存不是数据本身,是一个地址(指针)or 主键等数据
什么情况下需要回表?
情形1,myisam数据储存引擎级结构决定了它的查询都需要进行一次回表操作
情形2,覆盖索引不需要回表,
什么是覆盖索引,覆盖索引就是例如:就是将A B联合索引起来,然后查询 (主键)id A B就是覆盖索引
情形3,非覆盖索引,例如:建立联合索引 A B 然后查询的是 * 就都需要回表操作
开发的时候需要注意什么?
书写规范的sql语句。
尽量建立覆盖索引,然后查询数据
4,mysql三范式
第一范式,数据具有原子性,即每一列的数据都不能再拆分
第二范式,建立在第一范式的基础上,所以非主键字段都要依赖主键,而不能依赖主键的一部分
第三范式,建立在第二范式的基础上,非主键列只依赖主键,不依赖其他非主键
8,mysql日志
错误日志:
这种日志记录的内容包括服务器的启动和关闭,以及与问题或异常条件有关的消息。如果服务器无法启动,就可以查看此类日志。它会在终止之前把消息写到出错日志,以指明出现了什么问题。
普通查询日志:
这种日志记录的内容包括客户端连接、从客户端接收到的SQL语句和其他杂项事件。监视服务器的活动很有用处,从中可以了解到:'连接方是谁'、'它来之哪里',以及'它们在做什么'。当你想要查明客户端在往服务器发送什么样的语句时,使用日志是最方便的,它能为故障诊断或调试提供帮助。
慢查询日志:
这种日志可以帮你把那些可能需要被重新、以求或得更好性能的语句识别出来。默认情况下,如果某个查询花费的时间超过了10秒。它便会被认为很慢,并且服务器会将它写到慢查询日志里。有几个系统弄变量可以进一步控制慢查询日志所记录的内容。
二进制日志及二进制索引文件:
这种日志由一个或多个文件构成,记录由update、delete、insert、create table、drop table、grant等语句所做的修改。写到二进制日志里的内容都是一些以二进制格式编码的数据修改'事件'。二进制日志文件都伴随有一个索引文件,其中列出了当时存在的那些二进制日志文件。
用途:
1,在服务器崩溃之后,它可与备份一起配合完成表的恢复。首先,根据备份文件恢复数据库,然后,使用mysqlbinlog将二进制日志的内容转换为文本语句,并使用备份之后修改数据库的那些语句作为客户端mysql输入,把数据库恢复到崩溃时的状态。
2,它形成了复制的基础,储存在二进制日志里的数据修改事件可被传输到复制从服务器。
中继日志及中继日志索引文件
如果服务器是一个复制从服务器,它就会维护一个中继日志,其中包含的是从主服务器接收到的需要被执行的数据修改事件记录。中继日志文件的格式与二进制日志文件的格式相同,它有一个索引文件,其中列出的是从服务器上存在的日志文件。
redo日志 undo日志(innodb)
undo日志是保证数据的原子性
redo日志是保证数据的持久性
事务ACID特性是如何实现的?
事务的四种特性大家都知道是原子性、一致性、隔离性和持久性。也就是ACID,那么这四种特性是如何实现的呢?
隔离性是由锁机制实现
剩下的三种特性是由事务的redo和undo日志实现:
1,redo log 是重做日志,可以再写入,恢复提交事务修改的数据页操作,来保证事务的持久性。
2,undo log 是回滚日志,回滚行记录到某个保存点,来保证事务的原子性和一致性。
理解: redo log 是储存引擎层(innodb)生成的日志,记录的是物理层面的对数据页的操作,比如在某数据页某偏移量下写入或删除了数据。
undo log 是储存引擎层(innodb)生成的日志,记录的是逻辑层面的操作,且记录的都是相反的操作,比如对某行数据进行了insert操作,undo日志就会记载相反的操作delete,主要是用于事务的回滚。
redo和undo本质上都是一种恢复操作。
undo是把对数据表的操作回滚回去的行为,而redo操作是比如对表数据进行了操作,但是没有刷新到磁盘上时,数据库宕机了,在数据库重启后,可以去redo日志中找回这种操作的记录,继续执行。
redo日志
数据库的储存引擎(innodb)是以数据页为单位来管理储存空间的,所有的数据都放在磁盘中的。加载一个数据页中的数据,需要先把这个数据页从磁盘缓存到内存中(放在buffer pool中)然后才访问。
我们对数据库的增删改查操作都是对内存中数据的修改,然后内存再将数据的变动以一定的频率刷新到磁盘上。
引出一个问题:
为什么不直接修改磁盘中的数据,而是先将数据加载到内存中,在内存里修改,然后再更新到磁盘上?
因:因为cpu更新内容存中的数据比更新磁盘中的数据要快。
为什么需要redo日志?
1,内存缓冲池的存在可以消除cpu和磁盘之间的沟壑
2,checkpoint刷盘机制可以保证数据最终刷新到磁盘上
但存在一个问题就是,刷盘机制不是改一点刷一点,而是一个间隙时间内刷新一次,如果在间隙时间内数据库宕机,那么这个间隙时间更新的数据就无法刷新到磁盘上,导致数据丢失。
3,这和数据库持久性相矛盾,持久性要求一旦事务,那么事务修改的数据就会永久保存,即使系统崩溃。
那如何解决这个矛盾,来保证持久性?
解决办法就是,每次对内存数据的刷新,都把操作记录下来就好了。就算数据库宕机,我们也可以重启数据库后,重新执行记录的操作,就可以将没有成功刷新的数据刷新到磁盘上。
4,innodb采用了'日志先行'策略,每次修改,先写日志,再写磁盘,只有日志写入成功,才算事务提交成功。
5,日志就是redo日志。如果刷盘期间数据库宕机,就可以通过redo日志来恢复,进一步保证了四大特性中的持久性。
redo日志特点、好处
好处:
1,相比于直接刷磁盘,redo日志降低了刷盘次数
2,redo日志占用的空间非常小
特点:
1,顺序写入(执行事务时,一条语句可能产生若干个语句,这些日志语句时按顺序写入磁盘的,是顺序io,效率比随机io快。
2,不间断记录。redo日志记录不间断,区别于bin日志一个事务提交才整个记录一次
redo日志的组成
分为俩个部分:缓冲区和日志文件区
缓冲区(redo log buffer)
1,缓冲区是内存层面的
2,缓冲区默认大小16M,最小1M,最大4096M
3,缓冲区会将空间划分成一个个的缓冲块
日志文件区(redo log file)
1,日志文件是磁盘层面的
2,查看mysql目录,其中 logfile0 和 logfile1就是redo日志的文件区
redo日志运行流程(无图,需要理解)
过程:
1,将原始数据从磁盘读入内存中,修改内存中的数据拷贝
2,生成一条日志写入 redo log buffer 中,记录的是数据修改的值
3,当事务提交时,将 redo log buffer 中的内容刷新到redo log file,对 redo log file 采取的是追加写的方式。
4,隔一段时间就将内存中修改的数据刷新到磁盘中
5,hash
什么是hash?
hash表,其思想主要是基于数组支持按照下表随机访问数据时间复杂度为O(1)的特性。
什么是hash冲突?
我们知道,对象hash的前提是实现equals()和hashCode()两个方法,那么hashCode()的作用就是保证对象返回唯一的hash值,但当两个对象计算值一样时,就产生了hash冲突。
当我们对某个元素进行哈希计算,得到一个储存地址,然后进行插入时,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。
哈希冲突如何解决?
方法1,再哈希法,再哈希法又叫双哈希法,有多个不同的hash函数,如果遇到哈希冲突时,使用第二个、第三个再进哈希计算,直到没有冲突为止。
方法2,链地址法(拉链法),例如HashMap,每个哈希表接点都有一个next指针,多个哈希表接点可以用next指针构成一个单向链表。
方法3,建立公共溢出区法,在创建哈希表的同时,再额外创建一个公共溢出区,专门用来存放发生哈希冲突的元素,查找时,先冲哈希表差,查不到再去公共溢出区查。
方法4,理解1,开放地址法(再hash法),当关键字key的哈希地址p出现冲突时,以p为基础,产生另外一个++++++++++++++++++++哈希地址p1,如果p1仍然冲突,产生另外一个哈希地址,直到找出一个不冲突的哈希地址pi,将相应的元素存入其中。
理解2,开放寻址的核心思想是,如果出现hasn冲突(hash冲突),我们就重新探测一个空闲位置,将其插入。比如:我们可以使用线性探测法,当我们往hash表中插入数据时,如果某个数据经过hash函数hash之后,储存位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,如果遍历到尾部都没有找到空闲位置,那么我们就再冲表头开始找,直到找到为止。
各种解决途径的优缺点
| 再哈希法 | 链地址法 | 建立公共溢出区 | 开放寻址法 | |
| 优点 | 查询效率高 | 处理冲突简单,无堆积现象 | 容易序列化 | |
| 缺点 | 计算耗时 | 查询效率低 | 占用空间很大 |
redis采用的是拉链法(链表法)解决的冲突,
redis中hash表接点中都带有一个*next,这样如果哈希冲突了,就采用单向链表的方式将值链起来。
负载因子与rehash
hash表中的负载因子 = 填入表中的元素个数 / hash表的长度
hash表负载因子越大,代表空闲位置越少,冲突也就越多,hash表的性能会下降。
hash表负载因子越小,代表空闲位子越多,则会造成内存不能合理利用,从而形成内存浪费。
因此负载因子不能越大,也不能越小。要控制在一个合理的范围内。此时就需要rehash、渐进rehash
rehash过程
redis中,每个字典会创建两个hash表ht[0],ht[1].数据会储存再ht[0]表接点中。如果负载因子过小或过大。
redis就会以ht[0].used字段扩展ht[1].size字段,ht[1].size = ht[0].used^2的2的N次方幂。然后将ht[0]中的数据一次或逐步转存到ht[1]中,数据转换过程中,会生成一个rehashidx标记0为渐进转换过程中,rehashids为-1时。转换完成。此时ht[0]节点表为空,然后删除ht[0]表。将ht[1]表设置为ht[0],另外创建一个空ht[1]表节点。
理解:
字典结构定义:
typedef struct dict{
//类型特定函数
dictType *type;
//私有数据
void *privdata;
//哈希表
dictht ht[2];
//rehash索引
//rehash不在进行时,值为-1
int trehashidx; /*rehashing not in progress if rehashidx == -1 */
} dict;
hash表结构定义;
typedef struct dictht{
//hash数组
dictEntry **table;
//哈希表大小
unsigned long size;
//哈希表大小掩码,用于计算索引值
//总是等于size-1
unsigned long sizemask;
//该hash表用的接点数量
unsigned long used;
}
hash表接点结构定义:
typedef struct dictEntry{
//键
void *key;
//值
union{
void *val;
uint64_tu64;
int64_ts64;
}v;
//指向下一个hash表接点,形成链表
struct dictEntry *next;
} dictEntry;
以上三个结构就构成了redis字典结构,为嵌套关系。
数据结构与算法
6,
算法
什么是算法?
算法:是指用来操作数据、解决程序问题的一组方法。对一定规范的输入,在有限的时间内获得所要求的输出。一个算法的优劣可以用空间复杂度和时间复杂度来衡量。
时间复杂度
常数阶O(1)
对数阶O(logN)
线性阶O(n)
线性对数阶O(nlogN)
平方阶O(n^2)
立方阶O(n^3)
k次方阶O(n^k)
指数阶(2^n)
空间复杂度
O(1)
O(n)
O(n^2)
数据结构
什么是数据结构?
数据结构是计算机储存、组织数据的方式。一种好的数据结构可以带来更高的运行或者储存效率。数据在内存中是呈线性排列的,但是我们可以使用指针等道具,构造出类似"树形"的复杂结构。
八个常见的数据结构
数组(array) |
链表(linked list) |
栈(stack) | 线性数据结构
队列(queue) |
树(二叉树|完全二叉树|b+tree) |
堆(heap) |
散列表(hash表) | 非线性数据结构
图(graph) |
数组:
数组的定义:
数组是一种线性表数据结构。它用一组连续的内存空间,储存一组具有相同类型的数据。
什么是连续的内存空间?
首先,我们来说说内存,内存是由一个个连续的内存单元组成的,每一个内存单元都有自己的地址。在这些内存单元中,有些被其他数据占用了,有些是空闲的。
然而数据中的每个元素,都储存在小小的内存单元中,并且元素之间紧密排列,既不能打乱元素的储存顺序,也不能跳过某个储存单元进行储存。
数组的随机访问
数组的随机访问是有一个寻址公式的,因为数据都储存在一块连续的内存单元格中切每个单元格都有自己的地址(计算机通过这个地址访问数据)且每个单元格大小都是一样的,公式如下;
a[i]_address = base_address + i * data_type_size
总结:
数组是使用一块连续的内存空间,储存相同类型的一组数据,其最大的优点是数组支持随机访问,因为数组可以通过数组下表(寻址公式)快速访问对应元素,时间复杂度为O(1)。
数组在删除元素和插入元素这两个操作比较低效,是因为数组为了保持数据的连续性,会涉及到数据的挪动,平均时间复杂度为O(n)。
链表:
链表是一种顺序结构,由相互链接的线性熟悉怒项目序列组成。因此,必须顺序访问数据,并且无法进行随机访问。链表提供了动态集的简单灵活的表示形式。
伪代码:
typedef struct LNode{
ElemType data;//数据域
struct Lnode *next;//指针域,指向后续
}LNode,*LinkList;
树(二叉树):
伪代码:
typedef struct BiTNode{
TElemType data;
struct BiTNode *lchild,*rchild;//左右孩子指针
}BitNode,*BiTree;
八种数据结构的优缺点:
1,数组
优点:按照索引查询元素的速度很快
缺点:数组的大小在创建后就确定了,不方便扩容;数组只能储存一种数据类型;添加,删除元素的操作很耗时间,因为要移动其他元素
2,链表
优点:链表在插入、删除的时候可以达到O(1)的时间复杂度并且链表克服了数组必须预先知道数据大小的缺点,从而可以实现灵活的内存动态管理。
缺点:含有其他接点的引用,占用内存空间大;查询元素需要遍历整个链表,耗时。
3,栈
栈按照'先进后出'的原则来储存数据,先插入的数据会被压入栈底,后插入的数据在栈顶,读出数据的时候,从栈顶开始依次读出。
4,队列
与栈不同,队列对两端进行定义,一端叫队头,另外一端就叫队尾。队头只允许删除操作(出队),队尾只允许插入操作(入队)
5,树(注意考题-二叉树的遍历(先序遍历、中序遍历、后序遍历))
1,二叉树:每个接点最多含有俩个子树,按照左右不同的表现形式又可以分为多种。
2,完全二叉树:对于一颗二叉树,假设其深度为d,除了第d层,其它各层的接点数目均已达到最大值,且第d层所有节点从左向右连续的紧密排列
3,满二叉树:一颗每一层的节点数都达到了最大值的二叉树
B树
一种对读写操作进行优化的自平衡的二叉查找树,能够保持数据有序,拥有多于俩个的子树
6,哈希表
是一种可以通过关键码值(key-value)直接访问的数据结构,它最大的特点就是结合了数组和链表的有点可以快速实现查找、插入和删除。
哈希函数在哈希表中起着非常关键的作用
它可以把任意长度的输入变成固定长度的输出,该输出就是哈希值。
哈希表是通过数组实现的,首先对key值进行hash算法得到一个数,然后对该数进行寻址算法计算,得到一个数组中的下标,通过该下标对数据进行存取,解决地址冲突常用方法有链表发
7,栈(未完,待续)
8,图(未完,待续)
番外篇:
IO多路复用
什么是io多路复用
IO多路复用,一种同步io模型,单个线程/进程可以同时处理多个io请求,一个进程/线程可以监视多个文件句柄,一旦某个文件句柄就绪,就能通知应用程序进行相应的读写操作,
没有文件句柄就绪时会阻塞应用程序,交出cpu.
多路是指网络连接,复用是指同一个进程/线程。
理解:一个进行/线程虽然任意时刻只能处理一个请求,但是处理每个请求时,耗时控制在1毫秒以内,这样1秒内可以处理上千个请求,把时间拉长看,多个请求就复用了一个进程/线程。这就是复用,这种思想类似于一个cpu并发多个进程,所以也叫做时分多路复用。
为什么会出现io多路复用机制?
同步阻塞io
单线程时
服务器采用单线程,当accept一个请求后,在recve和send调用阻塞时,将无法accept其他请求,必须等待上一个请求处理完成,不能处理并发
多线程时
服务器采用多线程,当accept一个请求后,开启线程recv,可以完成并发处理,但随着请求数增加需要增加系统线程,大量的线程占用很大的内存空间,并且线程切换会带来很大开销。
同步非阻塞
服务器端当accept一个请求后,加入fds集合,每次轮询一遍fds集合recv(非阻塞)数据,没有数据则返回错误,每次轮询所有fd会浪费cpu
io多路复用
服务器端采用单线程通过select/epoll等系统调用获得fd列表,遍历有事件的fd进行accept/recv/send,使其能支持更多的并发连接请求。
io多路复用的三种实现方式
1,select函数
2,epoll函数,epoll只在linux下工作,epoll应用redis、linux
3,poll函数
区别
| select | poll | epoll | |
| 数据结构 | bitmap | 数组 | 红黑树 |
| 最大链接数 | 1024 | 无上限 | 无上限 |
| 工作效率 | 轮询O(n) | 轮询O(n) | 回调O(1) |
| fd拷贝 | 每次调用select拷贝 | 每次调用poll拷贝 | fd首次调用epoll_ctl拷贝,每次调用epoll_wait不拷贝 |
二叉树(伪代码)
二叉树结构伪代码
typedef struct BitNode{
TElemType data;
struct BiTNode *lchild,*rchild;
}BitNode,*BiTree;
先序遍历的顺序创建二叉树
status CreateBiTree(BiTree &T){
scanf(&ch);
if(ch == '') T = NULL;
else
{
T = (BitNode *)malloc(sizeof(BitNode));
if(!T) exit(OVERFLOW);
T->data = ch;
CreateBiTree(T->lchild);
CreateBiTree(T->rchild);
}
return OK;
}
先序遍历
void PreOrderTraverse(BiTree &T)
{
if(T)
{
printf("%d ",T->data);
PreOrderTraverse(T->lchild);
PreOrderTraverse(T->rchild);
}
}
中序遍历
void InOrderTraverse(BiTree &T)
{
if(T)
{
InOrderTraverse(T->lchild);
printf("%d ",T->data);
InOrderTraverse(T->rchild);
}
}
后续遍历
void PostOrderTraverse(BiTree &T){
if(T)
{
PostOrderTraverse(T->lchild);
PostOrderTraverse(T->rchild);
printf("%d ",T->data);
}
}
一些散碎的知识点:
laravel 的生命周期:
laravel 的请求周期,laravel的声明周期从public\index.php开始,从public\index.php结束。
第一步,注册加载composer自动生成的class loader,就是初始化第三方依赖。
第二步,生成容器container, 并向容器注册核心组件,是从 bootstrap/app.php脚本获取laravel应用实例。
第三步,http内核继承自 illuminate\Foundation\Http\Kernel类,该类定义了一个bootstrappers数组,这个数组中的类在请求被执行前运行,这些bootstrapper配置了错误处理,日志,检测应用环境以及它在请求被处理前需要执行的任务。 http内核还定义了一系列所有请求在处理前需要经过的http中间件,这些中间件处理http会话的读写,判断应用是否处于维护模式/验证csrf令牌等等。
第四步,请求传递给路由 在laravel基础的服务启动之后,就要把请求传递给路由了,路由器将会分发请求到路由或控制器,同时运行所有路由指定的中间件。(中间件有前置中间件及后置中间件)
依赖注入:
概念:一个类的实现依赖另一个类的技术
依赖注入的三种实现方式:
1,构造函数依赖注入: 构造方法注入的弊端是Test2类依赖的类较多,实例化时参数列表会很长,容易发生混乱。 示例:
<?php
/*
定义接口
*/
interface Interface_1 {
public function foo();
}
/*
定义类,实现接口 Interface_1
*/
class Class_1 implements Interface_1{
function foo(){
$class_name = __CLASS__;
echo "我是{$class_name},我实现了接口Interface_1";
}
}
/*
实现依赖注入的类(构造函数注入)
*/
class Class_DI_Demo {
public $interface_1;
//构造函数注入,需要Interface_1的服务
function __construct(Interface_1 $interface_1){
$this->interface_1 = $interface_1;
}
}
$class1 = new Class_1();
//注入 $class1
$class_DI_Demo = new Class_DI_Demo($class1);
$class_DI_Demo->interface_1->foo();
//结果:
我是 class_1,我实现了接口Interface_1
2,setter方法依赖注入。 这种方式的弊端就是当类需要很多的时候,需要很多的set方法。 示例:
class book{
private $db;
private $file;
function setdb($db){
$this->db=$db;
}
function setfile($file){
$this->file=$file;
}
}
class file{}
class db{}
...
class test{
$book = new Book();
$book->setdb(new db());
$book->setfile(new file());
}
3,接口方式依赖注入 示例:
<?php
/** Class c*/
class c{
public function say(){
echo 'hello';
}
}
/* Class a*/
class a{
private $c;
public function testC(C $c){
$this->c = $c; //实例化创建C类
}
public function sayC(){
echo $this->c->say(); //调用C类中的方法
}
}
$c = new c();
$a = new a();
$a->testC($c);
$a->sayC();
控制反转(和上面依赖注入对照来理解): 概念:将类与类的依赖关系 从程序内部 提到外部 管理 控制反转: 不需要在 a中直接 new b, 而是让ioc容器将B传给我,这就叫 控制反转,控制反转是站在a的立场来看的,他是来获取b的。 控制反转是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机都是由自己把控的,而现在这种全力转移到第三方,比如转移交给了ioc容器,它就是一个专门用来创建对象的工厂(BeanFactory),你要什么对象,它就给你什么对象,有了ioc容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖ioc容器了,通过ioc容器来建立它们之间的关系。

本文详细梳理了PHP面试中的常见知识点,包括MySQL的存储引擎对比、索引类型、数据恢复机制,以及Redis的基础知识。还探讨了数据库事务的ACID特性,重点讲解了回表、聚簇与非聚簇索引,以及redo和undo日志在InnoDB中的作用。此外,文章还涉及了数据结构中的哈希冲突解决方法,如链地址法,并讨论了数据结构与算法的基本概念,包括时间复杂度和空间复杂度。最后,提到了IO多路复用的概念和在处理并发请求中的优势。
1776

被折叠的 条评论
为什么被折叠?



