diff --git a/Database.md b/Database.md index 3b4217a..c056ae6 100644 --- a/Database.md +++ b/Database.md @@ -600,6 +600,99 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; +# 数据库锁 + +从粒度上来说就是**表锁、页锁、行锁**。表锁有意向共享锁、意向排他锁、自增锁等。行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 `MyISAM`引擎就`不支持行锁`。 + +## 行锁(Record Locks) + +在 `InnoDB` 事务中,行锁通过给索引上的索引项加锁来实现。即只有通过索引条件检索数据,`InnoDB` 才使用行级锁,否则将使用表锁。行级锁定同样分为两种类型:`共享锁` 和`排他锁`,以及加锁前需要先获得的 `意向共享锁` 和 `意向排他锁`。 + +行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。 + +### 共享锁(Shared Locks) + +共享锁又称为 `S锁` 或 `读锁`。若事务T对数据对象A加上 `S锁`,则事务T `只能读A`;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。 + +- `select ... lock in share mode`: 会加`共享锁` + + + +### 排它锁(Exclusive Locks) + +排它锁又称为 `X锁` 或 `写锁`。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。 + +注意:排他锁会阻止其它事务再对其**锁定的数据**加读或写的锁,但是不加锁的就没办法控制了。 + +- `insert`、`update`、`delete`、`select ... for update`:会加`排它锁` + + + +## 行锁实现算法 + +### Record Lock(行锁) + +单个行记录上的锁,总是会去锁住索引记录。 + +行锁,顾名思义,是加在`索引行`(是索引行,不是数据行)上的锁。比如: + +`select * from user where id=1 and id=10 for update`,就会在`id=1`和`id=10`的索引行上加Record Lock。 + + + +### Gap Lock(间隙锁) + +间隙锁,它会锁住两个索引之间的区域。比如: + +`select * from user where id>1 and id<10 for update`,就会在id为(1,10)的索引区间上加Gap Lock。 + +想一下幻读的原因,其实就是行锁只能锁住行,但新插入记录这个动作,要更新的是记录之间的“`间隙`”。所以加入间隙锁来解决幻读。 + + + +### Next-Key Lock(间隙锁) + +也叫间隙锁,它是 Record Lock + Gap Lock 形成的一个闭区间锁。比如: + +`select * from user where id>=1 and id<=10 for update`,就会在id为[1,10]的索引闭区间上加 Next-Key Lock。 + +这样组合起来就有:行级共享锁,表级共享锁;行级排它锁,表级排它锁。 + + + +## MVCC + +**MVCC**主要是通过**版本链**和**ReadView**来实现的。在Mysql的InnoDB引擎中,只有**已提交读(READ COMMITTD)**和**可重复读(REPEATABLE READ)**这两种隔离级别下的事务采用了MVCC机制。 + +### 版本链 + +在InnoDB引擎表中,它的每一行记录中有两个必要的隐藏列: + +- `DATA_TRX_ID`:表示插入或更新该行的最后一个事务的事务标识符,同样删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。行中会有一个特殊位置来标记删除。 +- `DATA_ROLL_PTR`:存储了一个指针,它指向这条记录的上一个版本的位置,通过它来获得上一个版本的记录信息。 + +**作用**:解决了读和写的并发执行。 + + + +### ReadView + +ReadView主要存放的是当前事务操作时,系统中仍然活跃着的事务(事务开启后,没有提交或回滚的事务)。 + +- **ReadView数据结构**:ReadView是MySQL底层使用C++代码实现的一个结构体,主要的内部属性如下: + - `trx_ids`:数组,存储的是创建readview时,活跃事务链表里所有的事务ID + - `low_limit_id`:存储的是创建readview时,活跃事务链表里最大的事务ID + - `up_limit_id`:存储的是创建readview时,活跃事务链表里最小的事务ID + - `creator_trx_id`:当前readview所属事务的事务版本号 + +- **ReadView创建策略**:对于读提交和可重复读事务隔离级别来说,ReadView创建策略是不同的,这样才能保证隔离性不同 + - `可重复读隔离级别`:事务开启后,第一次查询的时候创建,之后一直不变,直到事务结束 + - `读提交隔离级别`:事务开启后,每一次读取都重新创建 + +也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 + + + # 事务 **什么叫事务?** @@ -608,27 +701,88 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; ## 事务特性(ACID) -- **原子性(Atomicity)**:事务是最小的执行单位,不允许分割。原子性确保动作要么全部完成,要么完全不起作用。 +### 原子性(Atomicity) + +**事务是最小的执行单位,不允许分割。原子性确保动作要么全部完成,要么完全不起作用。** + +原子性是依赖于回滚日志(`undo log`)实现的。当事务对数据库进行修改时,`InnoDB`会生成对应的 `undo log`;如果事务执行失败或调用了 `rollback`,导致`事务需要回滚`,便可以利用 `undo log` 中的信息将数据回滚到修改之前的样子。`undo log`属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB 会根据 `undo log` 的内容做与之前相反的工作: + +- 对于每个`insert`,回滚时会执行`delete` +- 对于每个 `delete`,回滚时会执行`insert` +- 对于每个 `update`,回滚时会执行一个相反的 `update`,把数据改回去 + +以`update`操作为例:当事务执行`update`时,其生成的`undo log`中会包含被修改行的主键、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到`update`之前的状态。 + + + +### 一致性(Consistency) + +**事务开始前和结束后,数据库的完整性约束没有被破坏**。比如A向B转账,不可能A扣了钱,B却没收到。 + +一致性是事务追求的最终目标,原子性、持久性和隔离性其实都是为了保证数据库状态的一致性。当然,都是数据库层面的保障,一致性的实现也需要应用层面进行保障。也就是你的业务,比如购买操作只扣除用户的余额,不减库存,肯定无法保证状态的一致。 + + + +### 隔离性(Isolation) + +**并发访问数据库时,一个事务不被其他事务所干扰。** + +隔离性是通过 `锁` 和 `MVCC`(多版本并发控制) 实现。InnoDB采用的MVCC实现方式是:在需要时,通过undo日志构造出历史版本。 -- **一致性(Consistency)**:执行事务前后,数据保持一致。 - 一个事务执行前后,应该使数据库从一个一致性状态转换为另一个一致性状态。比方说假设A、B两个人,共有5000元。那么无论A给B转多少钱,转多少次,总数仍然是5000没有改变。 -- **隔离性(Isolation)**:并发访问数据库时,一个事务不被其他事务所干扰。 +### 持久性(Durability) -- **持久性(Durability)**:一个事务被提交之后。对数据库中数据的改变是持久的,即使数据库发生故障。 +**一个事务被提交之后。对数据库中数据的改变是持久的,即使数据库发生故障。** + +`InnnoDB`有很多 log,持久性靠的是`redo log`。持久性肯定和写有关,`MySQL` 里经常说到的 `WAL`技术,`WAL`的全称是`Write-Ahead Logging`,它的关键点就是先写日志,再写磁盘。 + + + +**redo log** + +当有一条记录要更新时,`InnoDB`引擎就会先把记录写到`redo log`(并更新内存),这个时候更新就算完成了。在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。`redo log`有两个特点 + +- 大小固定,循环写 +- `crash-safe` + +对于`redo log`是有两阶段的:`commit` 和 `prepare`如果不使用“两阶段提交”,数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。 + + + +**Buffer Pool** + +InnoDB还提供了缓存,`Buffer Pool`中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲: + +- 当读取数据时,会先从`Buffer Pool`中读取,如果`Buffer Pool`中没有,则从磁盘读取后放入Buffer Pool +- 当向数据库写入数据时,会首先写入`Buffer Pool`,`Buffer Pool`中修改的数据会定期刷新到磁盘中 + +Buffer Pool 的使用大大提高了读写数据的效率,但是也带了新的问题:如果`MySQL`宕机,而此时 `Buffer Pool`中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。 + +**所以加入了 redo log。** 当数据修改时,除了修改`Buffer Pool`中的数据,还会在`redo log`记录这次操作。当事务提交时,会调用`fsync`接口对`redo log`进行刷盘。如果`MySQL`宕机,重启时可以读取`redo log`中的数据,对数据库进行恢复。 + +`redo log`采用的是WAL(`Write-ahead logging`,预写式日志),所有修改先写入日志,再更新到`Buffer Pool`,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。而且这样做还有两个优点: + +- 刷脏页是随机`IO`,`redo log` 顺序`IO` +- 刷脏页以Page为单位,一个Page上的修改整页都要写;而redo log 只包含真正需要写入的,无效 IO 减少 ## 隔离级别 -数据库事务隔离级别有4种,由低到高为:**Read uncommitted** 、**Read committed** 、**Repeatable read** 、**Serializable** 。而且,在事务的并发操作中可能会出现 **脏读、不可重复读、幻读** 问题。不做隔离操作则会出现: +数据库事务隔离级别有4种,由低到高为:**Read uncommitted** 、**Read committed** 、**Repeatable read** 、**Serializable** 。 -- **脏读**:事务A中读到了事务B中未提交的更新数据内容 -- **不可重复读**:读到其它事务已经提交后的**更新**数据,即一个事务范围内两个相同的查询却返回了不同数据 + + +**事务并发问题** + +在事务的并发操作中,不做隔离操作则可能会出现 **脏读、不可重复读、幻读** 问题: + +- **脏读**:**事务A中读到了事务B中未提交的更新数据内容**。然后B回滚操作,那么A读取到的数据是脏数据 +- **不可重复读**:**事务A读到事务B已经提交后的数据**。即事务A多次读取同一数据时,返回结果不一致 - **幻读**:事物A执行select后,事物B**增或删**了一条数据,事务A再执行同一条SQL后发现多或少了一条数据 -- **第一类丢失更新**:A事务撤销时,把已经提交的B事务的更新数据覆盖了 -- **第二类丢失更新**:A事务提交时,把已经提交的B事务的更新数据覆盖了 + +**小结**:不可重复读的和幻读很容易混淆,**不可重复读**侧重于**修改**,**幻读**侧重于**新增或删除**。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。 @@ -664,8 +818,8 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; **读未提交的数据库锁情况** -- 事务中读取数据:**未加锁** -- 事务中更新数据:**只对数据增加行级共享锁** +- 读取数据:**未加锁** +- 写入数据:**只对数据增加行级共享锁** @@ -677,8 +831,8 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; **读已提交的数据库锁情况** -- 事务中读取数据:**加行级共享锁(读到时才加锁),读完后立即释放** -- 事务中更新数据:**在更新时的瞬间对其加行级排它锁,直到事务结束才释放** +- 读取数据:**加行级共享锁(读到时才加锁),读完后立即释放** +- 写入数据:**在更新时的瞬间对其加行级排它锁,直到事务结束才释放** @@ -703,8 +857,8 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; **可重复读的数据库锁情况** -- 事务中读取数据:**开始读取的瞬间对其增加行级共享锁,直到事务结束才释放** -- 事务中更新数据:**开始更新的瞬间对其增加行级排他锁,直到事务结束才释放** +- 读取数据:**开始读取的瞬间对其增加行级共享锁,直到事务结束才释放** +- 写入数据:**开始更新的瞬间对其增加行级排他锁,直到事务结束才释放** @@ -716,8 +870,8 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; **可序列化的数据库锁情况** -- 事务中读取数据:**先对其加表级共享锁 ,直到事务结束才释放** -- 事务中更新数据:**先对其加表级排他锁 ,直到事务结束才释放** +- 读取数据:**先对其加表级共享锁 ,直到事务结束才释放** +- 写入数据:**先对其加表级排他锁 ,直到事务结束才释放** diff --git a/OS.md b/OS.md index 78d4db5..5839840 100644 --- a/OS.md +++ b/OS.md @@ -828,6 +828,106 @@ TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号 +### UDP + +![UDP-TCP](images/OS/UDP-TCP.png) + +**总结** + +- TCP 向上层提供面向连接的可靠服务 ,UDP 向上层提供无连接不可靠服务 +- UDP 没有 TCP 传输可靠,但是可以在实时性要求搞的地方有所作为 +- 对数据准确性要求高,速度可以相对较慢的,可以选用TCP + + + +### TCP数据可靠性 + +一句话:通过`校验和`、`序列号`、`确认应答`、`超时重传`、`连接管理`、`流量控制`、`拥塞控制`等机制来保证可靠性。 + +**(1)校验和** + +在数据传输过程中,将发送的数据段都当做一个16位的整数,将这些整数加起来,并且前面的进位不能丢弃,补在最后,然后取反,得到校验和。 + +发送方:在发送数据之前计算校验和,并进行校验和的填充。接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方进行比较。 + +**(2)序列号** + +TCP 传输时将每个字节的数据都进行了编号,这就是序列号。序列号的作用不仅仅是应答作用,有了序列号能够将接收到的数据根据序列号进行排序,并且去掉重复的数据。 + +**(3)确认应答** + +TCP 传输过程中,每次接收方接收到数据后,都会对传输方进行确认应答,也就是发送 ACK 报文,这个 ACK 报文中带有对应的确认序列号,告诉发送方,接收了哪些数据,下一次数据从哪里传。 + +**(4)超时重传** + +在进行 TCP 传输时,由于存在确认应答与序列号机制,也就是说发送方发送一部分数据后,都会等待接收方发送的 ACK 报文,并解析 ACK 报文,判断数据是否传输成功。如果发送方发送完数据后,迟迟都没有接收到接收方传来的 ACK 报文,那么就对刚刚发送的数据进行重发。 + +**(5)连接管理** + +就是指三次握手、四次挥手的过程。 + +**(6)流量控制** + +如果发送方的发送速度太快,会导致接收方的接收缓冲区填充满了,这时候继续传输数据,就会造成大量丢包,进而引起丢包重传等等一系列问题。TCP 支持根据接收端的处理能力来决定发送端的发送速度,这就是流量控制机制。 + +具体实现方式:接收端将自己的接收缓冲区大小放入 TCP 首部的『窗口大小』字段中,通过 ACK 通知发送端。 + +**(7)拥塞控制** + +TCP 传输过程中一开始就发送大量数据,如果当时网络非常拥堵,可能会造成拥堵加剧。所以 TCP 引入了`慢启动机制`,在开始发送数据的时候,先发少量的数据探探路。 + + + +### TCP协议如何提高传输效率 + +一句话:TCP 协议提高效率的方式有`滑动窗口`、`快重传`、`延迟应答`、`捎带应答`等。 + +**(1)滑动窗口** + +如果每一个发送的数据段,都要收到 ACK 应答之后再发送下一个数据段,这样的话我们效率很低,大部分时间都用在了等待 ACK 应答上了。 + +为了提高效率我们可以一次发送多条数据,这样就能使等待时间大大减少,从而提高性能。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。 + +**(2)快重传** + +`快重传`也叫`高速重发控制`。 + +那么如果出现了丢包,需要进行重传。一般分为两种情况: + +情况一:数据包已经抵达,ACK被丢了。这种情况下,部分ACK丢了并不影响,因为可以通过后续的ACK进行确认; + +情况二:数据包直接丢了。发送端会连续收到多个相同的 ACK 确认,发送端立即将对应丢失的数据重传。 + +**(3)延迟应答** + +如果接收数据的主机立刻返回ACK应答,这时候返回的窗口大小可能比较小。 + +- 假设接收端缓冲区为1M,一次收到了512K的数据;如果立刻应答,返回的窗口就是512K; +- 但实际上可能处理端处理速度很快,10ms之内就把512K的数据从缓存区消费掉了; +- 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来; +- 如果接收端稍微等一会在应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M; + +窗口越大,网络吞吐量就越大,传输效率就越高;我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。 + +**(4)捎带应答** + +在延迟应答的基础上,很多情况下,客户端服务器在应用层也是一发一收的。这时候常常采用捎带应答的方式来提高效率,而ACK响应常常伴随着数据报文共同传输。如:三次握手。 + + + +### TCP如何处理拥塞 + +网络拥塞现象是指到达通信网络中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。拥塞控制是处理网络拥塞现象的一种机制。 + +拥塞控制的四个阶段: + +- 慢启动 +- 拥塞避免 +- 快速重传 +- 快速恢复 + + + ## Socket 基于TCP协议的客户端和服务器工作: @@ -1796,6 +1896,74 @@ HTTP状态码由三个十进制数字组成,第一个十进制数字定义了 +## 常见问题 + +### http1.1和http2的区别 + +**HTTP1.1** + +- 持久连接 +- 请求管道化 +- 增加缓存处理(新的字段如cache-control) +- 增加 Host 字段、支持断点传输等 + +**HTTP2.0** + +- 二进制分帧 +- 多路复用(或连接共享) +- 头部压缩 +- 服务器推送 + + + +### HTTP 和HTTPS的区别 + +(1)HTTPS 协议需要到 CA 申请证书,一般免费证书较少,因而需要一定费用。 + +(2)HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。 + +(3)HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。 + +(4)HTTP 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。 + + + +### 对称加密和非对称加密 + +对称密钥加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大问题就是密钥发送问题,即`如何安全地将密钥发给对方`; + +而非对称加密是指使用一对非对称密钥,即`公钥`和`私钥`,公钥可以随意发布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息后,使用自己的私钥进行解密。 + +由于非对称加密的方式不需要发送用来解密的私钥,所以可以`保证安全性`;但是和对称加密比起来,它比较`慢`,所以我们还是要用对称加密来传送消息,但对称加密所使用的密钥我们可以通过非对称加密的方式发送出去。 + + + +### 常见状态码 + +1×× : 请求处理中,请求已被接受,正在处理 + +2×× : 请求成功,请求被成功处理 200 OK + +3×× : 重定向,要完成请求必须进行进一步处理 301 : 永久性转移 302 :暂时性转移 304 :已缓存 + +4×× : 客户端错误,请求不合法 400:Bad Request,请求有语法问题 403:拒绝请求 404:客户端所访问的页面不存在 + +5×× : 服务器端错误,服务器不能处理合法请求 500 :服务器内部错误 503 :服务不可用,稍等 + + + +### Session、Cookie 的区别 + +- session 在服务器端,cookie 在客户端(浏览器) +- session 默认被存储在服务器的一个文件里(不是内存) +- session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id) +- session 可以放在 文件、数据库、或内存中都可以。 +- 用户验证这种场合一般会用 session + + + + + # OS ## 处理器 diff --git a/images/OS/UDP-TCP.png b/images/OS/UDP-TCP.png new file mode 100644 index 0000000..563989b Binary files /dev/null and b/images/OS/UDP-TCP.png differ