1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-01 16:28:03 +08:00

Compare commits

...

14 Commits

Author SHA1 Message Date
Guide
94bf6cb25e
Merge pull request #2478 from chingwl/chingwl-patch-1
Update redis-data-structures-02.md
2024-09-08 13:15:59 +08:00
jingwl
9deb83cd76
Update redis-data-structures-02.md
'start 和 end 之间' 比 'start 和 end 之前' 更易于理解,不易产生误解。
2024-09-08 10:42:07 +08:00
Guide
223fd4f18f
Merge pull request #2477 from null-1024/patch-1
typo maven-best-practices.md
2024-09-07 17:37:37 +08:00
Guide
93b80ea985 [docs update]线程池参数常用公式修改 2024-09-07 17:36:39 +08:00
Guide
9d3ae71046 [docs update]新增面试题: MySQL 性能怎么优化? 2024-09-07 17:32:06 +08:00
Guide
9b62186e76 [docs update]完善深度分页优化 2024-09-07 17:31:31 +08:00
nullptr
40bcf6dacd
typo maven-best-practices.md 2024-09-06 23:42:07 +08:00
Guide
6e490b0c94
Merge pull request #2476 from massyot/patch-1
修改一处错别字
2024-09-06 16:36:47 +08:00
hamster
66c4141050
修改一处错别字 2024-09-05 21:56:37 +08:00
Guide
322abf45c3
Merge pull request #2473 from Dllragon/main
Update jvm-garbage-collection.md 引号反了
2024-09-05 12:20:44 +08:00
Guide
cb0bd01132
Merge pull request #2474 from Chaobk/main
删除多余字
2024-09-05 12:15:41 +08:00
chaobk
d4de9a025b 删除多余字 2024-09-04 23:20:39 +08:00
DAI LILONG
3c6e434d46
Update jvm-garbage-collection.md 标点调整与内容更新
修改了错误使用的中文冒号;更新默认使用G1的JDK范围至JDK22
2024-09-04 16:53:47 +08:00
DAI LILONG
42c70053ee
Update jvm-garbage-collection.md 引号反了
引号反了
2024-09-04 16:16:05 +08:00
8 changed files with 90 additions and 26 deletions

View File

@ -194,7 +194,7 @@ HTTP 状态码用于描述 HTTP 请求的结果,比如 2xx 就代表请求被
![HTTP/1.0 和 HTTP/1.1 对比](https://oss.javaguide.cn/github/javaguide/cs-basics/network/http1.1-vs-http2.0.png)
- **多路复用Multiplexing**HTTP/2.0 在同一连接上可以同时传输多个请求和响应(可以看作是 HTTP/1.1 中长链接的升级版本互不干扰。HTTP/1.1 则使用串行方式,每个请求和响应都需要独立的连接,而浏览器为了控制资源会有 6-8 个 TCP 连接限制。。这使得 HTTP/2.0 在处理多个请求时更加高效,减少了网络延迟和提高了性能。
- **多路复用Multiplexing**HTTP/2.0 在同一连接上可以同时传输多个请求和响应(可以看作是 HTTP/1.1 中长链接的升级版本互不干扰。HTTP/1.1 则使用串行方式,每个请求和响应都需要独立的连接,而浏览器为了控制资源会有 6-8 个 TCP 连接限制。。这使得 HTTP/2.0 在处理多个请求时更加高效,减少了网络延迟和提高了性能。
- **二进制帧Binary Frames**HTTP/2.0 使用二进制帧进行数据传输,而 HTTP/1.1 则使用文本格式的报文。二进制帧更加紧凑和高效,减少了传输的数据量和带宽消耗。
- **头部压缩Header Compression**HTTP/1.1 支持`Body`压缩,`Header`不支持压缩。HTTP/2.0 支持对`Header`压缩,使用了专门为`Header`压缩而设计的 HPACK 算法,减少了网络开销。
- **服务器推送Server Push**HTTP/2.0 支持服务器推送,可以在客户端请求一个资源时,将其他相关资源一并推送给客户端,从而减少了客户端的请求次数和延迟。而 HTTP/1.1 需要客户端自己发送请求来获取相关资源。

View File

@ -829,15 +829,44 @@ mysql> EXPLAIN SELECT `score`,`name` FROM `cus_order` ORDER BY `score` DESC;
[数据冷热分离详解](../../high-performance/data-cold-hot-separation.md)
### 常见的数据库优化方法有哪些
### MySQL 性能怎么优化
- [索引优化](./mysql-index.md)
- [读写分离和分库分表](../../high-performance/read-and-write-separation-and-library-subtable.md)
- [数据冷热分离](../../high-performance/data-cold-hot-separation.md)
- [SQL 优化](../../high-performance/sql-optimization.md)
- [深度分页优化](../../high-performance/deep-pagination-optimization.md)
- 适当冗余数据
- 使用更高的硬件配置
MySQL 性能优化是一个系统性工程,涉及多个方面,在面试中不可能面面俱到。因此,建议按照“点-线-面”的思路展开,从核心问题入手,再逐步扩展,展示出你对问题的思考深度和解决能力。
**1. 抓住核心:慢 SQL 定位与分析**
性能优化的第一步永远是找到瓶颈。面试时,建议先从 **慢 SQL 定位和分析** 入手,这不仅能展示你解决问题的思路,还能体现你对数据库性能监控的熟练掌握:
- **监控工具:** 介绍常用的慢 SQL 监控工具,如 **MySQL 慢查询日志**、**Performance Schema** 等,说明你对这些工具的熟悉程度以及如何通过它们定位问题。
- **EXPLAIN 命令:** 详细说明 `EXPLAIN` 命令的使用,分析查询计划、索引使用情况,可以结合实际案例展示如何解读分析结果,比如执行顺序、索引使用情况、全表扫描等。
**2. 由点及面:索引、表结构和 SQL 优化**
定位到慢 SQL 后,接下来就要针对具体问题进行优化。 这里可以重点介绍索引、表结构和 SQL 编写规范等方面的优化技巧:
- **索引优化:** 这是 MySQL 性能优化的重点,可以介绍索引的创建原则、覆盖索引、最左前缀匹配原则等。如果能结合你项目的实际应用来说明如何选择合适的索引,会更加分一些。
- **表结构优化:** 优化表结构设计,包括选择合适的字段类型、避免冗余字段、合理使用范式和反范式设计等等。
- **SQL 优化:** 避免使用 `SELECT *`、尽量使用具体字段、使用连接查询代替子查询、合理使用分页查询、批量操作等,都是 SQL 编写过程中需要注意的细节。
**3. 进阶方案:架构优化**
当面试官对基础优化知识比较满意时,可能会深入探讨一些架构层面的优化方案。以下是一些常见的架构优化策略:
- **读写分离:** 将读操作和写操作分离到不同的数据库实例,提升数据库的并发处理能力。
- **分库分表:** 将数据分散到多个数据库实例或数据表中,降低单表数据量,提升查询效率。但要权衡其带来的复杂性和维护成本,谨慎使用。
- **数据冷热分离**:根据数据的访问频率和业务重要性,将数据分为冷数据和热数据,冷数据一般存储在存储在低成本、低性能的介质中,热数据高性能存储介质中。
- **缓存机制:** 使用 Redis 等缓存中间件,将热点数据缓存到内存中,减轻数据库压力。这个非常常用,提升效果非常明显,性价比极高!
**4. 其他优化手段**
除了慢 SQL 定位、索引优化和架构优化,还可以提及一些其他优化手段,展示你对 MySQL 性能调优的全面理解:
- **连接池配置:** 配置合理的数据库连接池(如 **连接池大小**、**超时时间** 等),能够有效提升数据库连接的效率,避免频繁的连接开销。
- **硬件配置:** 提升硬件性能也是优化的重要手段之一。使用高性能服务器、增加内存、使用 **SSD** 硬盘等硬件升级,都可以有效提升数据库的整体性能。
**5.总结**
在面试中,建议按优先级依次介绍慢 SQL 定位、[索引优化](./mysql-index.md)、表结构设计和 [SQL 优化](../../high-performance/sql-optimization.md)等内容。架构层面的优化,如[读写分离和分库分表](../../high-performance/read-and-write-separation-and-library-subtable.md)、[数据冷热分离](../../high-performance/data-cold-hot-separation.md) 应作为最后的手段,除非在特定场景下有明显的性能瓶颈,否则不应轻易使用,因其引入的复杂性会带来额外的维护成本。
## MySQL 学习资料推荐

View File

@ -36,7 +36,7 @@ Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap, 只需
| ------------------------------------- | ---------------------------------------------------------------- |
| SETBIT key offset value | 设置指定 offset 位置的值 |
| GETBIT key offset | 获取指定 offset 位置的值 |
| BITCOUNT key start end | 获取 start 和 end 之值为 1 的元素个数 |
| BITCOUNT key start end | 获取 start 和 end 之值为 1 的元素个数 |
| BITOP operation destkey key1 key2 ... | 对一个或多个 Bitmap 进行运算,可用运算符有 AND, OR, XOR 以及 NOT |
**Bitmap 基本操作演示**

View File

@ -34,7 +34,11 @@ SELECT * FROM t_order WHERE id > 100000 AND id <= 100010 ORDER BY id
SELECT * FROM t_order WHERE id > 100000 LIMIT 10
```
这种优化方式限制比较大,且一般项目的 ID 也没办法保证完全连续。
这种基于 ID 范围的深度分页优化方式存在很大限制:
1. **ID 连续性要求高**: 实际项目中,数据库自增 ID 往往因为各种原因(例如删除数据、事务回滚等)导致 ID 不连续,难以保证连续性。
2. **排序问题**: 如果查询需要按照其他字段(例如创建时间、更新时间等)排序,而不是按照 ID 排序,那么这种方法就不再适用。
3. **并发场景**: 在高并发场景下,单纯依赖记录上次查询的最后一条记录的 ID 进行分页,容易出现数据重复或遗漏的问题。
### 子查询
@ -51,28 +55,42 @@ SELECT * FROM t_order WHERE id > 100000 LIMIT 10
SELECT * FROM t_order WHERE id >= (SELECT id FROM t_order limit 1000000, 1) LIMIT 10;
```
**工作原理**:
1. 子查询 `(SELECT id FROM t_order LIMIT 1000000, 1)` 会利用主键索引快速定位到第 1000001 条记录,并返回其 ID 值。
2. 主查询 `SELECT * FROM t_order WHERE id >= ... LIMIT 10` 将子查询返回的起始 ID 作为过滤条件,使用 `id >=` 获取从该 ID 开始的后续 10 条记录。
不过,子查询的结果会产生一张新表,会影响性能,应该尽量避免大量使用子查询。并且,这种方法只适用于 ID 是正序的。在复杂分页场景,往往需要通过过滤条件,筛选到符合条件的 ID此时的 ID 是离散且不连续的。
当然,我们也可以利用子查询先去获取目标分页的 ID 集合,然后再根据 ID 集合获取内容,但这种写法非常繁琐,不如使用 INNER JOIN 延迟关联。
### 延迟关联
延迟关联的优化思路,跟子查询的优化思路其实是一样的:都是把条件转移到主键索引树,减少回表的次数。不同点是,延迟关联使用了 INNER JOIN内连接 包含子查询。
延迟关联与子查询的优化思路类似,都是通过将 `LIMIT` 操作转移到主键索引树上,减少回表次数。相比直接使用子查询,延迟关联通过 `INNER JOIN` 将子查询结果集成到主查询中,避免了子查询可能产生的临时表。在执行 `INNER JOIN`MySQL 优化器能够利用索引进行高效的连接操作(如索引扫描或其他优化策略),因此在深度分页场景下,性能通常优于直接使用子查询。
```sql
SELECT t1.* FROM t_order t1
INNER JOIN (SELECT id FROM t_order limit 1000000, 10) t2
ON t1.id = t2.id;
-- 使用 INNER JOIN 进行延迟关联
SELECT t1.*
FROM t_order t1
INNER JOIN (SELECT id FROM t_order LIMIT 1000000, 10) t2 ON t1.id = t2.id;
```
**工作原理**:
1. 子查询 `(SELECT id FROM t_order LIMIT 1000000, 10)` 利用主键索引快速定位目标分页的 10 条记录的 ID。
2. 通过 `INNER JOIN` 将子查询结果与主表 `t_order` 关联,获取完整的记录数据。
除了使用 INNER JOIN 之外,还可以使用逗号连接子查询。
```sql
-- 使用逗号进行延迟关联
SELECT t1.* FROM t_order t1,
(SELECT id FROM t_order limit 1000000, 10) t2
WHERE t1.id = t2.id;
```
**注意**: 虽然逗号连接子查询也能实现类似的效果,但为了代码可读性和可维护性,建议使用更规范的 `INNER JOIN` 语法。
### 覆盖索引
索引中已经包含了所有需要获取的字段的查询方式称为覆盖索引。
@ -89,7 +107,19 @@ ORDER BY code
LIMIT 1000000, 10;
```
不过,当查询的结果集占表的总行数的很大一部分时,可能就不会走索引了,自动转换为全表扫描。当然了,也可以通过 `FORCE INDEX` 来强制查询优化器走索引,但这种提升效果一般不明显。
**⚠️注意**:
- 当查询的结果集占表的总行数的很大一部分时MySQL 查询优化器可能选择放弃使用索引,自动转换为全表扫描。
- 虽然可以使用 `FORCE INDEX` 强制查询优化器走索引,但这种方式可能会导致查询优化器无法选择更优的执行计划,效果并不总是理想。
## 总结
本文总结了几种常见的深度分页优化方案:
1. **范围查询**: 基于 ID 连续性进行分页,通过记录上一页最后一条记录的 ID 来获取下一页数据。适合 ID 连续且按 ID 查询的场景,但在 ID 不连续或需要按其他字段排序时存在局限。
2. **子查询**: 先通过子查询获取分页的起始主键值,再根据主键进行筛选分页。利用主键索引提高效率,但子查询会生成临时表,复杂场景下性能不佳。
3. **延迟关联 (INNER JOIN)**: 使用 `INNER JOIN` 将分页操作转移到主键索引上,减少回表次数。相比子查询,延迟关联的性能更优,适合大数据量的分页查询。
4. **覆盖索引**: 通过索引直接获取所需字段,避免回表操作,减少 IO 开销适合查询特定字段的场景。但当结果集较大时MySQL 可能会选择全表扫描。
## 参考

View File

@ -423,7 +423,7 @@ public class ConditionalCompilation
首先,我们发现,在反编译后的代码中没有`System.out.println("Hello, ONLINE!");`,这其实就是条件编译。当`if(ONLINE)`为 false 的时候,编译器就没有对其内的代码进行编译。
所以,**Java 语法的条件编译,是通过判断条件为常量的 if 语句实现的。其原理也是 Java 语言的语法糖。根据 if 判断条件的真假,编译器直接把分支为 false 的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在整个 Java 类的结构或者类的属性上进行条件编译,这与 C/C++的条件编译相比,确实更有局限性。在 Java 语言设计之初并没有引入条件编译的功能,虽有局限,但是总比没有更强。**
所以,**Java 语法的条件编译,是通过判断条件为常量的 if 语句实现的。其原理也是 Java 语言的语法糖。根据 if 判断条件的真假,编译器直接把分支为 false 的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在整个 Java 类的结构或者类的属性上进行条件编译,这与 C/C++的条件编译相比,确实更有局限性。在 Java 语言设计之初并没有引入条件编译的功能,虽有局限,但是总比没有更强。**
### 断言

View File

@ -143,8 +143,13 @@ public final class NamingThreadFactory implements ThreadFactory {
有一个简单并且适用面比较广的公式:
- **CPU 密集型任务(N+1)** 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 NCPU 核心数)+1。比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断或者其它原因导致的任务暂停而带来的影响。一旦任务暂停CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
- **I/O 密集型任务(2N)** 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
- **CPU 密集型任务 (N)** 这种任务消耗的主要是 CPU 资源,线程数应设置为 NCPU 核心数)。由于任务主要瓶颈在于 CPU 计算能力,与核心数相等的线程数能够最大化 CPU 利用率,过多线程反而会导致竞争和上下文切换开销。
- **I/O 密集型任务(M \* N)** 这类任务大部分时间处理 I/O 交互,线程在等待 I/O 时不占用 CPU。 为了充分利用 CPU 资源,线程数可以设置为 M \* N其中 N 是 CPU 核心数M 是一个大于 1 的倍数,建议默认设置为 2 ,具体取值取决于 I/O 等待时间和任务特点,需要通过测试和监控找到最佳平衡点。
CPU 密集型任务不再推荐 N+1原因如下
- "N+1" 的初衷是希望预留线程处理突发暂停,但实际上,处理缺页中断等情况仍然需要占用 CPU 核心。
- CPU 密集场景下CPU 始终是瓶颈,预留线程并不能凭空增加 CPU 处理能力,反而可能加剧竞争。
**如何判断是 CPU 密集任务还是 IO 密集任务?**

View File

@ -353,7 +353,7 @@ JDK1.2 以后Java 对引用的概念进行了扩充,将引用分为强引
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 Java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择“标记-复制”算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
**延伸面试问题:** HotSpot 为什么要分为新生代和老年代?
@ -367,8 +367,8 @@ JDK1.2 以后Java 对引用的概念进行了扩充,将引用分为强引
JDK 默认垃圾收集器(使用 `java -XX:+PrintCommandLineFlags -version` 命令查看):
- JDK 8Parallel Scavenge新生代+ Parallel Old老年代
- JDK 9 ~ JDK20: G1
- JDK 8: Parallel Scavenge新生代+ Parallel Old老年代
- JDK 9 ~ JDK22: G1
### Serial 收集器

View File

@ -23,12 +23,12 @@ Maven 遵循标准目录结构来保持项目之间的一致性。遵循这种
Maven 项目的标准目录结构如下:
```groovy
src /
main /
src/
main/
java/
resources/
test/ java
/
test/
java/
resources/
pom.xml
```