Skip to content

Commit 899256d

Browse files
committed
update0613
1 parent 83b2e65 commit 899256d

File tree

6 files changed

+274
-3
lines changed

6 files changed

+274
-3
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
MySQL中编写SQL时,遵循良好的习惯能够提高查询性能、保障数据一致性、提升代码可读性和维护性。以下列举了多个编写SQL的好习惯
2+
3+
#### 1.使用EXPLAIN分析查询计划
4+
5+
在编写或优化复杂查询时,先使用EXPLAIN命令查看查询执行计划,理解MySQL如何执行查询、访问哪些表、使用哪种类型的联接以及索引的使用情况。
6+
7+
好处:有助于识别潜在的性能瓶颈,如全表扫描、错误的索引选择、过多的临时表或文件排序等,从而针对性地优化查询或调整索引结构。
8+
9+
#### 2.避免全表扫描
10+
11+
2. 习惯:尽可能利用索引来避免全表扫描,尤其是在处理大表时。确保在WHERE、JOIN条件和ORDER BY、GROUP BY子句中使用的列有适当的索引。
12+
13+
好处:极大地减少数据访问量,提高查询性能,减轻I/O压力。
14+
15+
#### 3. 为表和字段添加注释
16+
17+
3. 习惯:在创建表时,为表和每个字段添加有意义的注释,描述其用途、数据格式、业务规则等信息。
18+
19+
好处:提高代码可读性和可维护性,帮助其他开发人员快速理解表结构和字段含义,减少沟通成本和误解。
20+
21+
#### 4. 明确指定INSERT语句的列名
22+
23+
习惯:在INSERT语句中显式列出要插入数据的列名,即使插入所有列也应如此。
24+
25+
好处:避免因表结构变化导致的插入错误,增强代码的健壮性,同时也提高了语句的清晰度。
26+
27+
#### 5. 格式化SQL语句
28+
29+
习惯:保持SQL语句的格式整洁,使用一致的大小写(如关键词大写、表名和列名小写),合理缩进,避免过长的单行语句。
30+
31+
好处:提高代码可读性,便于审查、调试和团队协作。
32+
33+
#### 6. 使用LIMIT限制结果集大小
34+
35+
习惯:在执行SELECT、DELETE或UPDATE操作时,若不需要处理全部数据,务必使用LIMIT子句限制结果集大小,特别是在生产环境中。
36+
37+
好处:防止因误操作导致大量数据被修改或删除,降低风险,同时也能提高查询性能。
38+
39+
#### 7.使用JOIN语句代替子查询
40+
41+
习惯:在可能的情况下,优先使用JOIN操作代替嵌套的子查询,特别是在处理多表关联查询时。
42+
43+
好处:许多情况下JOIN的执行效率高于子查询,而且JOIN语句通常更易于理解和优化。
44+
45+
#### 8.避免在WHERE子句中对NULL进行比较
46+
47+
习惯:使用IS NULL和IS NOT NULL来检查字段是否为NULL,而不是直接与NULL进行等值或不等值比较。
48+
49+
好处:正确处理NULL值,避免逻辑错误和未预期的结果。
50+
51+
#### 9.避免在查询中使用SELECT
52+
53+
习惯:明确列出需要的列名,而不是使用SELECT *从表中获取所有列。
54+
55+
好处:减少网络传输的数据量,降低I/O开销,提高查询性能,同时也有利于代码的清晰性和可维护性。
56+
57+
#### 10. 数据库对象命名规范
58+
59+
习惯:遵循一致且有意义的命名约定,如使用小写字母、下划线分隔单词,避免使用MySQL保留字,保持表名、列名、索引名等的简洁性和一致性。
60+
61+
好处:提高代码可读性,减少命名冲突,便于团队协作和维护。
62+
63+
#### 11. 事务管理
64+
65+
习惯:对一系列需要保持原子性的操作使用事务管理,确保数据的一致性。
66+
67+
好处:在发生异常时能够回滚未完成的操作,避免数据处于不一致状态。
68+
69+
#### 12.适时使用索引覆盖
70+
71+
习惯:对于只查询索引列且不需要访问数据行的查询(如计数、统计),创建覆盖索引以避免回表操作。
72+
73+
好处:极大提升查询性能,减少I/O开销。
74+
75+
#### 13.遵循第三范式或适当反范式
76+
77+
习惯:根据业务需求和查询模式,合理设计表结构,遵循第三范式以减少数据冗余和更新异常,或适当反范式以优化查询性能。
78+
79+
好处:保持数据一致性,减少数据维护成本,或提高查询效率。
80+
81+
#### 14.使用预编译语句(PreparedStatement)
82+
83+
习惯:在应用程序中使用预编译语句(如Java中的PreparedStatement)执行SQL,特别是对于动态拼接SQL语句的情况。
84+
85+
好处:避免SQL注入攻击,提高查询性能,减少数据库服务器的解析开销。
86+
87+
#### 15.定期分析与优化表和索引
88+
89+
习惯:定期运行ANALYZE TABLE收集统计信息,以便MySQL优化器做出更准确的查询计划决策。根据查询性能监控结果,适时调整索引或重构表结构。
90+
91+
好处:确保数据库持续高效运行,适应不断变化的业务需求和数据分布。

docs/database/mysql.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ head:
2222

2323
## 更新记录
2424

25+
- 2024.06.05,更新[MySQL查询 limit 1000,10 和limit 10 速度一样快吗?](###MySQL查询 limit 1000,10 和limit 10 速度一样快吗?)
26+
2527
- 2024.5.15,新增[B树和B+树的区别?](###B树和B+树的区别?)
2628

2729
## 什么是MySQL
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
一条 SQL 查询语句如何执行的
2+
3+
在平常的开发中,可能很多人都是 CRUD,对 SQL 语句的语法很熟练,但是说起一条 SQL 语句在 MySQL 中是怎么执行的却浑然不知,今天大彬就由浅入深,带大家一点点剖析一条 SQL 语句在 MySQL 中是怎么执行的。
4+
5+
比如你执行下面这个 SQL 语句时,我们看到的只是输入一条语句,返回一个结果,却不知道 MySQL 内部的执行过程:
6+
7+
```mysql
8+
mysql> select * from T where ID=10
9+
```
10+
11+
在剖析这个语句怎么执行之前,我们先看一下 MySQL 的基本架构示意图,能更清楚的看到 SQL 语句在 MySQL 的各个功能模块中的执行过程。
12+
13+
![](http://img.topjavaer.cn/img/202406030841887.png)
14+
15+
整体来说,MySQL 可以分为 Server 层和存储引擎两部分。
16+
17+
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
18+
19+
### 连接器
20+
21+
如果要操作 MySQL 数据库,我们必须使用 MySQL 客户端来连接 MySQL 服务器,这时候就是服务器中的连接器来负责根客户端建立连接、获取权限、维持和管理连接。
22+
23+
在和服务端完成 TCP 连接后,连接器就要认证身份,需要用到用户名和密码,确保用户有足够的权限执行该SQL语句。
24+
25+
### 查询缓存
26+
27+
建立完连接后,就可以执行查询语句了,来到第二步:查询缓存。
28+
29+
MySQL 拿到第一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中,如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。
30+
31+
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
32+
33+
### 分析器
34+
35+
如果没有命中缓存,就要开始真正执行语句了,MySQL 首先会对 SQL 语句做解析。
36+
37+
分析器会先做 “词法分析”,MySQL 需要识别出 SQL 里面的字符串分别是什么,代表什么。
38+
39+
做完之后就要做“语法分析”,根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。若果语句不对,就会收到错误提醒。
40+
41+
### 优化器
42+
43+
经过了分析器,MySQL 就知道要做什么了,但是在开始执行之前,要先经过优化器的处理。
44+
45+
比如:优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。
46+
47+
MySQL 会帮我去使用他自己认为的最好的方式去优化这条 SQL 语句,并生成一条条的执行计划,比如你创建了多个索引,MySQL 会依据**成本最小原则**来选择使用对应的索引,这里的成本主要包括两个方面, IO 成本和 CPU 成本。
48+
49+
### 执行器
50+
51+
执行优化之后的执行计划,在开始执行之前,先判断一下用户对这个表有没有执行查询的权限,如果没有,就会返回没有权限的错误;如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
52+
53+
### 存储引擎
54+
55+
执行器将查询请求发送给存储引擎组件。
56+
57+
存储引擎组件负责具体的数据存储、检索和修改操作。
58+
59+
存储引擎根据执行器的请求,从磁盘或内存中读取或写入相关数据。
60+
61+
### 返回结果
62+
63+
存储引擎将查询结果返回给执行器。
64+
65+
执行器将结果返回给连接器。
66+
67+
最后,连接器将结果发送回客户端,完成整个执行过程。

docs/java/java-concurrent.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ head:
2222

2323
## 线程池
2424

25-
线程池:一个管理线程的池子。
25+
### 什么是线程池,如何使用?为什么要使用线程池?
26+
27+
线程池就是事先将多个线程对象放到一个容器中,使用的时候就不用new线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高了代码执行效率。
2628

2729
### 为什么平时都是使用线程池创建线程,直接new一个线程不好吗?
2830

@@ -35,7 +37,7 @@ head:
3537

3638
系统资源有限,每个人针对不同业务都可以手动创建线程,并且创建线程没有统一标准,比如创建的线程有没有名字等。当系统运行起来,所有线程都在抢占资源,毫无规则,混乱场面可想而知,不好管控。
3739

38-
**频繁手动创建线程为什么开销会大?跟new Object() 有什么差别?**
40+
### 频繁手动创建线程为什么开销会大?跟new Object() 有什么差别?
3941

4042
虽然Java中万物皆对象,但是new Thread() 创建一个线程和 new Object()还是有区别的。
4143

@@ -733,9 +735,23 @@ class SeasonThreadTask implements Runnable{
733735

734736
## ThreadLocal
735737

738+
### ThreadLocal是什么
739+
736740
线程本地变量。当使用`ThreadLocal`维护变量时,`ThreadLocal`为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程。
737741

738-
### ThreadLocal原理
742+
### 为什么要使用ThreadLocal?
743+
744+
并发场景下,会存在多个线程同时修改一个共享变量的场景。这就可能会出现**线性安全问题**
745+
746+
为了解决线性安全问题,可以用加锁的方式,比如使用`synchronized` 或者`Lock`。但是加锁的方式,可能会导致系统变慢。
747+
748+
还有另外一种方案,就是使用空间换时间的方式,即使用`ThreadLocal`。使用`ThreadLocal`类访问共享变量时,会在每个线程的本地,都保存一份共享变量的拷贝副本。多线程对共享变量修改时,实际上操作的是这个变量副本,从而保证线性安全。
749+
750+
### Thread和ThreadLocal有什么联系呢?
751+
752+
Thread和ThreadLocal是绑定的, ThreadLocal依赖于Thread去执行, Thread将需要隔离的数据存放到ThreadLocal(准确的讲是ThreadLocalMap)中,来实现多线程处理。
753+
754+
### 说说ThreadLocal的原理?
739755

740756
每个线程都有一个`ThreadLocalMap``ThreadLocal`内部类),Map中元素的键为`ThreadLocal`,而值对应线程的变量副本。
741757

docs/other/site-diary.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ sidebar: heading
88

99
## 更新记录
1010

11+
- 2024.06.11,新增-聊聊如何用Redis 实现分布式锁?
12+
13+
- 2024.06.07,更新-为什么Redis单线程还这么快?
14+
15+
- 2024.05.21,新增一条SQL是怎么执行的
16+
1117
- 2023.12.28,[增加源码解析模块,包含Sprign/SpringMVC/MyBatis(更新中)](/source/mybatis/1-overview.html)
1218

1319
- 2023.12.28,[遭受黑客攻击,植入木马程序](/zsxq/article/site-hack.html)

docs/redis/redis.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -824,6 +824,95 @@ Redis 的哈希表使用链地址法(separate chaining)来解决键冲突:
824824

825825
原理跟 Java 的 HashMap 类似,都是数组+链表的结构。当发生 hash 碰撞时将会把元素追加到链表上。
826826

827+
## Redis实现分布式锁有哪些方案?
828+
829+
在这里分享六种Redis分布式锁的正确使用方式,由易到难。
830+
831+
方案一:SETNX+EXPIRE
832+
833+
方案二:SETNX+value值(系统时间+过期时间)
834+
835+
方案三:使用Lua脚本(包含SETNX+EXPIRE两条指令)
836+
837+
方案四::ET的扩展命令(SETEXPXNX)
838+
839+
方案五:开源框架~Redisson
840+
841+
方案六:多机实现的分布式锁Redlock
842+
843+
**首先什么是分布式锁**
844+
845+
分布式锁是一种机制,用于确保在分布式系统中,多个节点在同一时刻只能有一个节点对共享资源进行操作。它是解决分布式环境下并发控制和数据一致性问题的关键技术之一。
846+
847+
分布式锁的特征:
848+
849+
1、「互斥性」:任意时刻,只有一个客户端能持有锁。
850+
851+
2、「锁超时释放」:持有锁超时,可以释放,防止不必要的资源浪费,也可以防止死锁。
852+
853+
3、「可重入性」“一个线程如果获取了锁之后,可以再次对其请求加锁。
854+
855+
4、「安全性」:锁只能被持有的客户端删除,不能被其他客户端删除
856+
857+
5、「高性能和高可用」:加锁和解锁需要开销尽可能低,同时也要保证高可用,避免分布式锁失效。
858+
859+
860+
861+
**Redis分布式锁方案一:SETNX+EXPIRE**
862+
863+
提到Redis的分布式锁,很多朋友可能就会想到setnx+expire命令。即先用setnx来抢锁,如果抢到之后,再用expire给锁设置一个过期时间,防止锁忘记了释放。SETNX是SETIF NOT EXISTS的简写。日常命令格式是SETNXkey value,如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。假设某电商网站的某商品做秒杀活动,key可以设置为key_resource_id,value设置任意值,伪代码如下:
864+
865+
![img](https://cdn.nlark.com/yuque/0/2024/png/28848830/1718076327854-c75a4b72-4a8a-4afb-87fe-378082b36046.png)
866+
867+
缺陷:加锁与设置过期时间是非原子操作,如果加锁后未来得及设置过期时间系统异常等,会导致其他线程永远获取不到锁。
868+
869+
**Redis分布式锁方案二**:SETNX+value值(系统时间+过期时间)
870+
871+
为了解决方案一,「发生异常锁得不到释放的场景」,有小伙伴认为,可以把过期时间放到setnx的value值里面。如果加锁失败,再拿出value值校验一下即可。
872+
873+
这个方案的优点是,避免了expire 单独设置过期时间的操作,把「过期时间放到setnx的value值」里面来。解决了方案一发生异常,锁得不到释放的问题。
874+
875+
但是这个方案有别的缺点:过期时间是客户端自己生成的(System.currentTimeMillis()是当前系统的时间),必须要求分布式环境下,每个客户端的时间必须同步。如果锁过期的时候,并发多个客户端同时请求过来,都执行jedis.get()和set(),最终只能有一个客户端加锁成功,但是该客户端锁的过期时间,可能被别的客户端覆盖。该锁没有保存持有者的唯一标识,可能坡别的客户端释放/解锁
876+
877+
**分布式锁方案三:使用Lua脚本(包含SETNX+EXPIRE两条指令)**
878+
879+
实际上,我们还可以使用Lua脚本来保证原子性(包含setnx和expire两条指令),lua脚本如下:
880+
881+
![img](https://cdn.nlark.com/yuque/0/2024/png/28848830/1718075869527-a3704805-53a6-4bd4-be07-2558cff533a2.png)
882+
883+
加锁代码如下:
884+
885+
![img](https://cdn.nlark.com/yuque/0/2024/png/28848830/1718075859795-a0cfcfe0-7c56-49ac-9182-6b203739a99e.png)
886+
887+
**Redis分布式锁方案四:SET的扩展命令(SET EX PX NX)**
888+
889+
除了使用,使用Lua脚本,保证SETNX+EXPIRE两条指令的原子性,我们还可以巧用Redis的SET指令扩展参数。(`SET key value[EX seconds]`PX milliseconds][NX|XX]`),它也是原子性的
890+
891+
`SET key value[EX seconds][PX milliseconds][NX|XX]`
892+
893+
1. NX:表示key不存在的时候,才能set成功,也即保证只有第一个客户端请求才能获得锁,而其他客户端请求只能等其释放锁, 才能获取。
894+
2. EXseconds:设定key的过期时间,时间单位是秒。
895+
3. PX milliseconds:设定key的过期时间,单位为毫秒。
896+
4. XX:仅当key存在时设置值。
897+
898+
伪代码如下:
899+
900+
![img](https://cdn.nlark.com/yuque/0/2024/png/28848830/1718075985907-86dd8066-001a-4957-a998-897cdc27c831.png)
901+
902+
**Redis分布式锁方案五:Redisson框架**
903+
904+
方案四还是可能存在「锁过期释放,业务没执行完」的问题。设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。当前开源框架Redisson解决了这个问题。一起来看下Redisson底层原理图:
905+
906+
![img](https://cdn.nlark.com/yuque/0/2024/png/28848830/1718076061807-8b2419dd-13ff-441e-a238-30bf402b07fb.png)
907+
908+
只要线程一加锁成功,就会启动一个watchdog看门狗,它是一个后台线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会不断的延长锁key的生存时间。因此,Redisson就是使用Redisson解决了「锁过期释放,业务没执行完」问题。
909+
910+
**分布式锁方案六:多机实现的分布式锁Redlock+Redisson**
911+
912+
前面五种方案都是基于单机版的讨论,那么集群部署该怎么处理?
913+
914+
答案是多机实现的分布式锁Redlock+Redisson
915+
827916

828917

829918
![](http://img.topjavaer.cn/img/20220612101342.png)

0 commit comments

Comments
 (0)