mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-16 18:10:13 +08:00
[docs update]数据库和网络部分问题答案优化完善
This commit is contained in:
parent
ff77b0cabf
commit
3b1767d6e3
@ -11,31 +11,61 @@ tag:
|
||||
|
||||
### TCP 与 UDP 的区别(重要)
|
||||
|
||||
1. **是否面向连接**:UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。
|
||||
2. **是否是可靠传输**:远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
|
||||
3. **是否有状态**:这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 ,TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(**这很渣男!**)。
|
||||
4. **传输效率**:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。
|
||||
5. **传输形式**:TCP 是面向字节流的,UDP 是面向报文的。
|
||||
6. **首部开销**:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。
|
||||
7. **是否提供广播或多播服务**:TCP 只支持点对点通信,UDP 支持一对一、一对多、多对一、多对多;
|
||||
1. **是否面向连接**:
|
||||
- TCP 是面向连接的。在传输数据之前,必须先通过“三次握手”建立连接;数据传输完成后,还需要通过“四次挥手”来释放连接。这保证了双方都准备好通信。
|
||||
- UDP 是无连接的。发送数据前不需要建立任何连接,直接把数据包(数据报)扔出去。
|
||||
2. **是否是可靠传输**:
|
||||
- TCP 提供可靠的数据传输服务。它通过序列号、确认应答 (ACK)、超时重传、流量控制、拥塞控制等一系列机制,来确保数据能够无差错、不丢失、不重复且按顺序地到达目的地。
|
||||
- UDP 提供不可靠的传输。它尽最大努力交付 (best-effort delivery),但不保证数据一定能到达,也不保证到达的顺序,更不会自动重传。收到报文后,接收方也不会主动发确认。
|
||||
3. **是否有状态**:
|
||||
- TCP 是有状态的。因为要保证可靠性,TCP 需要在连接的两端维护连接状态信息,比如序列号、窗口大小、哪些数据发出去了、哪些收到了确认等。
|
||||
- UDP 是无状态的。它不维护连接状态,发送方发出数据后就不再关心它是否到达以及如何到达,因此开销更小(**这很“渣男”!**)。
|
||||
4. **传输效率**:
|
||||
- TCP 因为需要建立连接、发送确认、处理重传等,其开销较大,传输效率相对较低。
|
||||
- UDP 结构简单,没有复杂的控制机制,开销小,传输效率更高,速度更快。
|
||||
5. **传输形式**:
|
||||
- TCP 是面向字节流 (Byte Stream) 的。它将应用程序交付的数据视为一连串无结构的字节流,可能会对数据进行拆分或合并。
|
||||
- UDP 是面向报文 (Message Oriented) 的。应用程序交给 UDP 多大的数据块,UDP 就照样发送,既不拆分也不合并,保留了应用程序消息的边界。
|
||||
6. **首部开销**:
|
||||
- TCP 的头部至少需要 20 字节,如果包含选项字段,最多可达 60 字节。
|
||||
- UDP 的头部非常简单,固定只有 8 字节。
|
||||
7. **是否提供广播或多播服务**:
|
||||
- TCP 只支持点对点 (Point-to-Point) 的单播通信。
|
||||
- UDP 支持一对一 (单播)、一对多 (多播/Multicast) 和一对所有 (广播/Broadcast) 的通信方式。
|
||||
8. ……
|
||||
|
||||
我把上面总结的内容通过表格形式展示出来了!确定不点个赞嘛?
|
||||
为了更直观地对比,可以看下面这个表格:
|
||||
|
||||
| | TCP | UDP |
|
||||
| ---------------------- | -------------- | ---------- |
|
||||
| 是否面向连接 | 是 | 否 |
|
||||
| 是否可靠 | 是 | 否 |
|
||||
| 是否有状态 | 是 | 否 |
|
||||
| 传输效率 | 较慢 | 较快 |
|
||||
| 传输形式 | 字节流 | 数据报文段 |
|
||||
| 首部开销 | 20 ~ 60 bytes | 8 bytes |
|
||||
| 是否提供广播或多播服务 | 否 | 是 |
|
||||
| 特性 | TCP | UDP |
|
||||
| ------------ | -------------------------- | ----------------------------------- |
|
||||
| **连接性** | 面向连接 | 无连接 |
|
||||
| **可靠性** | 可靠 | 不可靠 (尽力而为) |
|
||||
| **状态维护** | 有状态 | 无状态 |
|
||||
| **传输效率** | 较低 | 较高 |
|
||||
| **传输形式** | 面向字节流 | 面向数据报 (报文) |
|
||||
| **头部开销** | 20 - 60 字节 | 8 字节 |
|
||||
| **通信模式** | 点对点 (单播) | 单播、多播、广播 |
|
||||
| **常见应用** | HTTP/HTTPS, FTP, SMTP, SSH | DNS, DHCP, SNMP, TFTP, VoIP, 视频流 |
|
||||
|
||||
### 什么时候选择 TCP,什么时候选 UDP?
|
||||
|
||||
- **UDP 一般用于即时通信**,比如:语音、 视频、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。
|
||||
- **TCP 用于对传输准确性要求特别高的场景**,比如文件传输、发送和接收邮件、远程登录等等。
|
||||
选择 TCP 还是 UDP,主要取决于你的应用**对数据传输的可靠性要求有多高,以及对实时性和效率的要求有多高**。
|
||||
|
||||
当**数据准确性和完整性至关重要,一点都不能出错**时,通常选择 TCP。因为 TCP 提供了一整套机制(三次握手、确认应答、重传、流量控制等)来保证数据能够可靠、有序地送达。典型应用场景如下:
|
||||
|
||||
- **Web 浏览 (HTTP/HTTPS):** 网页内容、图片、脚本必须完整加载才能正确显示。
|
||||
- **文件传输 (FTP, SCP):** 文件内容不允许有任何字节丢失或错序。
|
||||
- **邮件收发 (SMTP, POP3, IMAP):** 邮件内容需要完整无误地送达。
|
||||
- **远程登录 (SSH, Telnet):** 命令和响应需要准确传输。
|
||||
- ......
|
||||
|
||||
当**实时性、速度和效率优先,并且应用能容忍少量数据丢失或乱序**时,通常选择 UDP。UDP 开销小、传输快,没有建立连接和保证可靠性的复杂过程。典型应用场景如下:
|
||||
|
||||
- **实时音视频通信 (VoIP, 视频会议, 直播):** 偶尔丢失一两个数据包(可能导致画面或声音短暂卡顿)通常比因为等待重传(TCP 机制)导致长时间延迟更可接受。应用层可能会有自己的补偿机制。
|
||||
- **在线游戏:** 需要快速传输玩家位置、状态等信息,对实时性要求极高,旧的数据很快就没用了,丢失少量数据影响通常不大。
|
||||
- **DHCP (动态主机配置协议):** 客户端在请求 IP 时自身没有 IP 地址,无法满足 TCP 建立连接的前提条件,并且 DHCP 有广播需求、交互模式简单以及自带可靠性机制。
|
||||
- **物联网 (IoT) 数据上报:** 某些场景下,传感器定期上报数据,丢失个别数据点可能不影响整体趋势分析。
|
||||
- ......
|
||||
|
||||
### HTTP 基于 TCP 还是 UDP?
|
||||
|
||||
|
@ -553,31 +553,26 @@ MVCC 在 MySQL 中实现所依赖的手段主要是: **隐藏字段、read view
|
||||
|
||||
### SQL 标准定义了哪些事务隔离级别?
|
||||
|
||||
SQL 标准定义了四个隔离级别:
|
||||
SQL 标准定义了四种事务隔离级别,用来平衡事务的隔离性(Isolation)和并发性能。级别越高,数据一致性越好,但并发性能可能越低。这四个级别是:
|
||||
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server)的默认隔离级别。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且,InnoDB 在此级别下通过 MVCC(多版本并发控制) 和 Next-Key Locks(间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
|
||||
- **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
|
||||
|
||||
---
|
||||
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|
||||
| :--------------: | :--: | :--------: | :--: |
|
||||
| READ-UNCOMMITTED | √ | √ | √ |
|
||||
| READ-COMMITTED | × | √ | √ |
|
||||
| REPEATABLE-READ | × | × | √ |
|
||||
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
|
||||
| ---------------- | ----------------- | -------------------------------- | ---------------------- |
|
||||
| READ UNCOMMITTED | √ | √ | √ |
|
||||
| READ COMMITTED | × | √ | √ |
|
||||
| REPEATABLE READ | × | × | √ (标准) / ≈× (InnoDB) |
|
||||
| SERIALIZABLE | × | × | × |
|
||||
|
||||
### MySQL 的隔离级别是基于锁实现的吗?
|
||||
|
||||
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
|
||||
|
||||
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
|
||||
|
||||
### MySQL 的默认隔离级别是什么?
|
||||
|
||||
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
|
||||
MySQL InnoDB 存储引擎的默认隔离级别是 **REPEATABLE READ**。可以通过以下命令查看:
|
||||
|
||||
- MySQL 8.0 之前:`SELECT @@tx_isolation;`
|
||||
- MySQL 8.0 及之后:`SELECT @@transaction_isolation;`
|
||||
|
||||
```sql
|
||||
mysql> SELECT @@tx_isolation;
|
||||
@ -590,6 +585,12 @@ mysql> SELECT @@tx_isolation;
|
||||
|
||||
关于 MySQL 事务隔离级别的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。
|
||||
|
||||
### MySQL 的隔离级别是基于锁实现的吗?
|
||||
|
||||
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
|
||||
|
||||
SERIALIZABLE 隔离级别是通过锁来实现的,READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
|
||||
|
||||
## MySQL 锁
|
||||
|
||||
锁是一种常见的并发事务的控制方式。
|
||||
|
@ -11,43 +11,46 @@ tag:
|
||||
|
||||
## 事务隔离级别总结
|
||||
|
||||
SQL 标准定义了四个隔离级别:
|
||||
SQL 标准定义了四种事务隔离级别,用来平衡事务的隔离性(Isolation)和并发性能。级别越高,数据一致性越好,但并发性能可能越低。这四个级别是:
|
||||
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
|
||||
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
|
||||
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server)的默认隔离级别。
|
||||
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且,InnoDB 在此级别下通过 MVCC(多版本并发控制) 和 Next-Key Locks(间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
|
||||
- **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
|
||||
|
||||
---
|
||||
|
||||
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|
||||
| :--------------: | :--: | :--------: | :--: |
|
||||
| READ-UNCOMMITTED | √ | √ | √ |
|
||||
| READ-COMMITTED | × | √ | √ |
|
||||
| REPEATABLE-READ | × | × | √ |
|
||||
| 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
|
||||
| ---------------- | ----------------- | -------------------------------- | ---------------------- |
|
||||
| READ UNCOMMITTED | √ | √ | √ |
|
||||
| READ COMMITTED | × | √ | √ |
|
||||
| REPEATABLE READ | × | × | √ (标准) / ≈× (InnoDB) |
|
||||
| SERIALIZABLE | × | × | × |
|
||||
|
||||
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
|
||||
**默认级别查询:**
|
||||
|
||||
```sql
|
||||
MySQL> SELECT @@tx_isolation;
|
||||
+-----------------+
|
||||
| @@tx_isolation |
|
||||
+-----------------+
|
||||
MySQL InnoDB 存储引擎的默认隔离级别是 **REPEATABLE READ**。可以通过以下命令查看:
|
||||
|
||||
- MySQL 8.0 之前:`SELECT @@tx_isolation;`
|
||||
- MySQL 8.0 及之后:`SELECT @@transaction_isolation;`
|
||||
|
||||
```bash
|
||||
mysql> SELECT @@transaction_isolation;
|
||||
+-------------------------+
|
||||
| @@transaction_isolation |
|
||||
+-------------------------+
|
||||
| REPEATABLE-READ |
|
||||
+-----------------+
|
||||
+-------------------------+
|
||||
```
|
||||
|
||||
从上面对 SQL 标准定义了四个隔离级别的介绍可以看出,标准的 SQL 隔离级别定义里,REPEATABLE-READ(可重复读)是不可以防止幻读的。
|
||||
**InnoDB 的 REPEATABLE READ 对幻读的处理:**
|
||||
|
||||
但是!InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况:
|
||||
标准的 SQL 隔离级别定义里,REPEATABLE READ 是无法防止幻读的。但 InnoDB 的实现通过以下机制很大程度上避免了幻读:
|
||||
|
||||
- **快照读**:由 MVCC 机制来保证不出现幻读。
|
||||
- **当前读**:使用 Next-Key Lock 进行加锁来保证不出现幻读,Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的结合,行锁只能锁住已经存在的行,为了避免插入新行,需要依赖间隙锁。
|
||||
- **快照读 (Snapshot Read)**:普通的 SELECT 语句,通过 **MVCC** 机制实现。事务启动时创建一个数据快照,后续的快照读都读取这个版本的数据,从而避免了看到其他事务新插入的行(幻读)或修改的行(不可重复读)。
|
||||
- **当前读 (Current Read)**:像 `SELECT ... FOR UPDATE`, `SELECT ... LOCK IN SHARE MODE`, `INSERT`, `UPDATE`, `DELETE` 这些操作。InnoDB 使用 **Next-Key Lock** 来锁定扫描到的索引记录及其间的范围(间隙),防止其他事务在这个范围内插入新的记录,从而避免幻读。Next-Key Lock 是行锁(Record Lock)和间隙锁(Gap Lock)的组合。
|
||||
|
||||
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ** 并不会有任何性能损失。
|
||||
值得注意的是,虽然通常认为隔离级别越高、并发性越差,但 InnoDB 存储引擎通过 MVCC 机制优化了 REPEATABLE READ 级别。对于许多常见的只读或读多写少的场景,其性能**与 READ COMMITTED 相比可能没有显著差异**。不过,在写密集型且并发冲突较高的场景下,RR 的间隙锁机制可能会比 RC 带来更多的锁等待。
|
||||
|
||||
InnoDB 存储引擎在分布式事务的情况下一般会用到 SERIALIZABLE 隔离级别。
|
||||
此外,在某些特定场景下,如需要严格一致性的分布式事务(XA Transactions),InnoDB 可能要求或推荐使用 SERIALIZABLE 隔离级别来确保全局数据的一致性。
|
||||
|
||||
《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章这样写到:
|
||||
|
||||
|
@ -83,7 +83,11 @@ public class RpcRequest implements Serializable {
|
||||
|
||||
~~`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。~~
|
||||
|
||||
**🐛 修正(参见:[issue#2174](https://github.com/Snailclimb/JavaGuide/issues/2174))**:`static` 修饰的变量是静态变量,属于类而非类的实例,本身是不会被序列化的。然而,`serialVersionUID` 是一个特例,`serialVersionUID` 的序列化做了特殊处理。当一个对象被序列化时,`serialVersionUID` 会被写入到序列化的二进制流中;在反序列化时,也会解析它并做一致性判断,以此来验证序列化对象的版本一致性。如果两者不匹配,反序列化过程将抛出 `InvalidClassException`,因为这通常意味着序列化的类的定义已经发生了更改,可能不再兼容。
|
||||
**🐛 修正(参见:[issue#2174](https://github.com/Snailclimb/JavaGuide/issues/2174))**:
|
||||
|
||||
通常情况下,`static` 变量是属于类的,不属于任何单个对象实例,所以它们本身不会被包含在对象序列化的数据流里。序列化保存的是对象的状态(也就是实例变量的值)。然而,`serialVersionUID` 是一个特例,`serialVersionUID` 的序列化做了特殊处理。关键在于,`serialVersionUID` 不是作为对象状态的一部分被序列化的,而是被序列化机制本身用作一个特殊的“指纹”或“版本号”。
|
||||
|
||||
当一个对象被序列化时,`serialVersionUID` 会被写入到序列化的二进制流中(像是在保存一个版本号,而不是保存 `static` 变量本身的状态);在反序列化时,也会解析它并做一致性判断,以此来验证序列化对象的版本一致性。如果两者不匹配,反序列化过程将抛出 `InvalidClassException`,因为这通常意味着序列化的类的定义已经发生了更改,可能不再兼容。
|
||||
|
||||
官方说明如下:
|
||||
|
||||
@ -91,7 +95,7 @@ public class RpcRequest implements Serializable {
|
||||
>
|
||||
> 如果想显式指定 `serialVersionUID` ,则需要在类中使用 `static` 和 `final` 关键字来修饰一个 `long` 类型的变量,变量名字必须为 `"serialVersionUID"` 。
|
||||
|
||||
也就是说,`serialVersionUID` 只是用来被 JVM 识别,实际并没有被序列化。
|
||||
也就是说,`serialVersionUID` 本身(作为 static 变量)确实不作为对象状态被序列化。但是,它的值被 Java 序列化机制特殊处理了——作为一个版本标识符被读取并写入序列化流中,用于在反序列化时进行版本兼容性检查。
|
||||
|
||||
**如果有些字段不想进行序列化怎么办?**
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user