1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

[docs update]数据库和网络部分问题答案优化完善

This commit is contained in:
Guide 2025-04-25 07:00:55 +08:00
parent ff77b0cabf
commit 3b1767d6e3
5 changed files with 543 additions and 448 deletions

View File

@ -11,31 +11,61 @@ tag:
### TCP 与 UDP 的区别(重要) ### TCP 与 UDP 的区别(重要)
1. **是否面向连接**UDP 在传送数据之前不需要先建立连接。而 TCP 提供面向连接的服务,在传送数据之前必须先建立连接,数据传送结束后要释放连接。 1. **是否面向连接**
2. **是否是可靠传输**:远地主机在收到 UDP 报文后不需要给出任何确认并且不保证数据不丢失不保证是否顺序到达。TCP 提供可靠的传输服务TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。 - TCP 是面向连接的。在传输数据之前,必须先通过“三次握手”建立连接;数据传输完成后,还需要通过“四次挥手”来释放连接。这保证了双方都准备好通信。
3. **是否有状态**这个和上面的“是否可靠传输”相对应。TCP 传输是有状态的,这个有状态说的是 TCP 会去记录自己发送消息的状态比如消息是否发送了、是否被接收了等等。为此 TCP 需要维持复杂的连接状态表。而 UDP 是无状态服务,简单来说就是不管发出去之后的事情了(**这很渣男!**)。 - UDP 是无连接的。发送数据前不需要建立任何连接,直接把数据包(数据报)扔出去。
4. **传输效率**:由于使用 TCP 进行传输的时候多了连接、确认、重传等机制,所以 TCP 的传输效率要比 UDP 低很多。 2. **是否是可靠传输**
5. **传输形式**TCP 是面向字节流的UDP 是面向报文的。 - TCP 提供可靠的数据传输服务。它通过序列号、确认应答 (ACK)、超时重传、流量控制、拥塞控制等一系列机制,来确保数据能够无差错、不丢失、不重复且按顺序地到达目的地。
6. **首部开销**TCP 首部开销20 60 字节)比 UDP 首部开销8 字节)要大。 - UDP 提供不可靠的传输。它尽最大努力交付 (best-effort delivery),但不保证数据一定能到达,也不保证到达的顺序,更不会自动重传。收到报文后,接收方也不会主动发确认。
7. **是否提供广播或多播服务**TCP 只支持点对点通信UDP 支持一对一、一对多、多对一、多对多; 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. …… 8. ……
我把上面总结的内容通过表格形式展示出来了!确定不点个赞嘛? 为了更直观地对比,可以看下面这个表格:
| | TCP | UDP | | 特性 | TCP | UDP |
| ---------------------- | -------------- | ---------- | | ------------ | -------------------------- | ----------------------------------- |
| 是否面向连接 | 是 | 否 | | **连接性** | 面向连接 | 无连接 |
| 是否可靠 | 是 | 否 | | **可靠性** | 可靠 | 不可靠 (尽力而为) |
| 是否有状态 | 是 | 否 | | **状态维护** | 有状态 | 无状态 |
| 传输效率 | 较慢 | 较快 | | **传输效率** | 较低 | 较高 |
| 传输形式 | 字节流 | 数据报文段 | | **传输形式** | 面向字节流 | 面向数据报 (报文) |
| 首部开销 | 20 60 bytes | 8 bytes | | **头部开销** | 20 - 60 字节 | 8 字节 |
| 是否提供广播或多播服务 | 否 | 是 | | **通信模式** | 点对点 (单播) | 单播、多播、广播 |
| **常见应用** | HTTP/HTTPS, FTP, SMTP, SSH | DNS, DHCP, SNMP, TFTP, VoIP, 视频流 |
### 什么时候选择 TCP什么时候选 UDP? ### 什么时候选择 TCP什么时候选 UDP?
- **UDP 一般用于即时通信**,比如:语音、 视频、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。 选择 TCP 还是 UDP主要取决于你的应用**对数据传输的可靠性要求有多高,以及对实时性和效率的要求有多高**。
- **TCP 用于对传输准确性要求特别高的场景**,比如文件传输、发送和接收邮件、远程登录等等。
当**数据准确性和完整性至关重要,一点都不能出错**时,通常选择 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 ### HTTP 基于 TCP 还是 UDP

View File

@ -553,31 +553,26 @@ MVCC 在 MySQL 中实现所依赖的手段主要是: **隐藏字段、read view
### SQL 标准定义了哪些事务隔离级别? ### SQL 标准定义了哪些事务隔离级别?
SQL 标准定义了四个隔离级别 SQL 标准定义了四种事务隔离级别用来平衡事务的隔离性Isolation和并发性能。级别越高数据一致性越好但并发性能可能越低。这四个级别是
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。 - **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。 - **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server的默认隔离级别。
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 - **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且InnoDB 在此级别下通过 MVCC多版本并发控制 和 Next-Key Locks间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
- **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。 - **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
--- | 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
| ---------------- | ----------------- | -------------------------------- | ---------------------- |
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | | READ UNCOMMITTED | √ | √ | √ |
| :--------------: | :--: | :--------: | :--: | | READ COMMITTED | × | √ | √ |
| READ-UNCOMMITTED | √ | √ | √ | | REPEATABLE READ | × | × | √ (标准) / ≈× (InnoDB) |
| READ-COMMITTED | × | √ | √ | | SERIALIZABLE | × | × | × |
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
### MySQL 的隔离级别是基于锁实现的吗?
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
SERIALIZABLE 隔离级别是通过锁来实现的READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
### MySQL 的默认隔离级别是什么? ### 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 ```sql
mysql> SELECT @@tx_isolation; mysql> SELECT @@tx_isolation;
@ -590,6 +585,12 @@ mysql> SELECT @@tx_isolation;
关于 MySQL 事务隔离级别的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。 关于 MySQL 事务隔离级别的详细介绍,可以看看我写的这篇文章:[MySQL 事务隔离级别详解](./transaction-isolation-level.md)。
### MySQL 的隔离级别是基于锁实现的吗?
MySQL 的隔离级别基于锁和 MVCC 机制共同实现的。
SERIALIZABLE 隔离级别是通过锁来实现的READ-COMMITTED 和 REPEATABLE-READ 隔离级别是基于 MVCC 实现的。不过, SERIALIZABLE 之外的其他隔离级别可能也需要用到锁机制,就比如 REPEATABLE-READ 在当前读情况下需要使用加锁读来保证不会出现幻读。
## MySQL 锁 ## MySQL 锁
锁是一种常见的并发事务的控制方式。 锁是一种常见的并发事务的控制方式。

View File

@ -11,43 +11,46 @@ tag:
## 事务隔离级别总结 ## 事务隔离级别总结
SQL 标准定义了四个隔离级别 SQL 标准定义了四种事务隔离级别用来平衡事务的隔离性Isolation和并发性能。级别越高数据一致性越好但并发性能可能越低。这四个级别是
- **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。 - **READ-UNCOMMITTED(读取未提交)** :最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。这种级别在实际应用中很少使用,因为它对数据一致性的保证太弱。
- **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。 - **READ-COMMITTED(读取已提交)** :允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。这是大多数数据库(如 Oracle, SQL Server的默认隔离级别。
- **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 - **REPEATABLE-READ(可重复读)** :对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 存储引擎的默认隔离级别正是 REPEATABLE READ。并且InnoDB 在此级别下通过 MVCC多版本并发控制 和 Next-Key Locks间隙锁+行锁) 机制,在很大程度上解决了幻读问题。
- **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。 - **SERIALIZABLE(可串行化)** :最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
--- | 隔离级别 | 脏读 (Dirty Read) | 不可重复读 (Non-Repeatable Read) | 幻读 (Phantom Read) |
| ---------------- | ----------------- | -------------------------------- | ---------------------- |
| READ UNCOMMITTED | √ | √ | √ |
| READ COMMITTED | × | √ | √ |
| REPEATABLE READ | × | × | √ (标准) / ≈× (InnoDB) |
| SERIALIZABLE | × | × | × |
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | **默认级别查询:**
| :--------------: | :--: | :--------: | :--: |
| READ-UNCOMMITTED | √ | √ | √ |
| READ-COMMITTED | × | √ | √ |
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ可重读**。我们可以通过`SELECT @@tx_isolation;`命令来查看MySQL 8.0 该命令改为`SELECT @@transaction_isolation;` MySQL InnoDB 存储引擎的默认隔离级别是 **REPEATABLE READ**。可以通过以下命令查看:
```sql - MySQL 8.0 之前:`SELECT @@tx_isolation;`
MySQL> SELECT @@tx_isolation; - MySQL 8.0 及之后:`SELECT @@transaction_isolation;`
+-----------------+
| @@tx_isolation | ```bash
+-----------------+ mysql> SELECT @@transaction_isolation;
| REPEATABLE-READ | +-------------------------+
+-----------------+ | @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
``` ```
从上面对 SQL 标准定义了四个隔离级别的介绍可以看出,标准的 SQL 隔离级别定义里REPEATABLE-READ(可重复读)是不可以防止幻读的。 **InnoDB 的 REPEATABLE READ 对幻读的处理:**
但是InnoDB 实现的 REPEATABLE-READ 隔离级别其实是可以解决幻读问题发生的,主要有下面两种情况 标准的 SQL 隔离级别定义里REPEATABLE READ 是无法防止幻读的。但 InnoDB 的实现通过以下机制很大程度上避免了幻读
- **快照读**:由 MVCC 机制来保证不出现幻读 - **快照读 (Snapshot Read)**:普通的 SELECT 语句,通过 **MVCC** 机制实现。事务启动时创建一个数据快照,后续的快照读都读取这个版本的数据,从而避免了看到其他事务新插入的行(幻读)或修改的行(不可重复读)
- **当前读**:使用 Next-Key Lock 进行加锁来保证不出现幻读Next-Key Lock 是行锁Record Lock和间隙锁Gap Lock的结合行锁只能锁住已经存在的行为了避免插入新行需要依赖间隙锁 - **当前读 (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 TransactionsInnoDB 可能要求或推荐使用 SERIALIZABLE 隔离级别来确保全局数据的一致性
《MySQL 技术内幕InnoDB 存储引擎(第 2 版)》7.7 章这样写到: 《MySQL 技术内幕InnoDB 存储引擎(第 2 版)》7.7 章这样写到:

View File

@ -83,7 +83,11 @@ public class RpcRequest implements Serializable {
~~`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。~~ ~~`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` ,则需要在类中使用 `static``final` 关键字来修饰一个 `long` 类型的变量,变量名字必须为 `"serialVersionUID"`
也就是说,`serialVersionUID` 只是用来被 JVM 识别,实际并没有被序列化 也就是说,`serialVersionUID` 本身(作为 static 变量)确实不作为对象状态被序列化。但是,它的值被 Java 序列化机制特殊处理了——作为一个版本标识符被读取并写入序列化流中,用于在反序列化时进行版本兼容性检查
**如果有些字段不想进行序列化怎么办?** **如果有些字段不想进行序列化怎么办?**