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

Compare commits

...

11 Commits

Author SHA1 Message Date
Guide
97ae1aeb1a
Merge pull request #2628 from YJRY/main
文档内容勘误
2025-03-02 18:02:13 +08:00
YJRY
86af2d5626
Merge branch 'Snailclimb:main' into main 2025-03-02 15:46:07 +08:00
xuqi
51b3caecec Redis常见面试题总结(下)
1、词句勘误和调整;
2、标点符号勘误和调整。
2025-03-02 15:43:49 +08:00
xuqi
fffb0f5100 Redis常见面试题总结(上)
1、词句勘误和调整;
2、标点符号勘误和调整。

MySQL索引详解
MySQL高性能优化规范建议总结
1、冒号调整。
2025-03-02 00:47:19 +08:00
Guide
83f39a33b8
Merge pull request #2627 from YJRY/main
文档内容修正
2025-03-01 15:53:29 +08:00
xuqi
f9eb32f623 MySQL高性能优化规范建议总结
1、专业术语名称调整。

MySQL索引详解
1、词句勘误和调整;
2、标点符号勘误和调整。
2025-03-01 00:57:27 +08:00
Guide
24d7fb1092
Merge pull request #2625 from lingfenghu/main
修正一处jvm参数表述不清
2025-03-01 00:16:57 +08:00
Guide
23fa4b81bd
Merge pull request #2626 from YJRY/main
“MySQL高性能优化规范建议总结”内容勘误
2025-03-01 00:15:21 +08:00
xuqi
374027a9ba 1、词句勘误和调整;
2、标点符号勘误和调整。
2025-02-28 22:31:57 +08:00
hulingfeng
8f6d7e9d6d fix: 修正一处表述不清的JVM参数实例 2025-02-27 10:46:02 +08:00
hulingfeng
a2b71a0ef2 fix: 修正一处表述不清的JVM参数实例 2025-02-27 10:44:47 +08:00
5 changed files with 323 additions and 322 deletions

View File

@ -11,17 +11,17 @@ tag:
## 数据库命名规范
- 所有数据库对象名称必须使用小写字母并用下划线分割
- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
- 数据库对象的命名要能做到见名识意,并且最后不要超过 32 个字符
- 临时库表必须以 `tmp_` 为前缀并以日期为后缀,备份表必须以 `bak_` 为前缀并以日期 (时间戳) 为后缀
- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
- 所有数据库对象名称必须使用小写字母并用下划线分割
- 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)
- 数据库对象的命名要能做到见名识义,并且最好不要超过 32 个字符。
- 临时库表必须以 `tmp_` 为前缀并以日期为后缀,备份表必须以 `bak_` 为前缀并以日期 (时间戳) 为后缀
- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
## 数据库基本设计规范
### 所有表必须使用 InnoDB 存储引擎
没有特殊要求(即 InnoDB 无法满足的功能如:列存储存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎MySQL5.5 之前默认使用 Myisam5.6 以后默认的为 InnoDB
没有特殊要求(即 InnoDB 无法满足的功能如:列存储存储空间数据等)的情况下,所有表必须使用 InnoDB 存储引擎MySQL5.5 之前默认使用 MyISAM5.6 以后默认的为 InnoDB
InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能更好。
@ -33,19 +33,19 @@ InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能
### 所有表和字段都需要添加注释
使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护
使用 comment 从句添加表和列的备注,从一开始就进行数据字典的维护
### 尽量控制单表数据量的大小,建议控制在 500 万以内
500 万并不是 MySQL 数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。
可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小
### 谨慎使用 MySQL 分区表
分区表在物理上表现为多个文件,在逻辑上表现为一个表
分区表在物理上表现为多个文件,在逻辑上表现为一个表
谨慎选择分区键,跨分区查询效率可能更低
谨慎选择分区键,跨分区查询效率可能更低
建议采用物理分表的方式管理大数据。
@ -71,7 +71,7 @@ InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能
### 禁止在线上做数据库压力测试
### 禁止从开发环境,测试环境直接连接生产环境数据库
### 禁止从开发环境测试环境直接连接生产环境数据库
安全隐患极大,要对生产环境抱有敬畏之心!
@ -79,22 +79,22 @@ InnoDB 支持事务,支持行级锁,更好的恢复性,高并发下性能
### 优先选择符合存储需要的最小的数据类型
存储字节越小,占用也就空间越小,性能也越好。
存储字节越小,占用空间也就越小,性能也越好。
**a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整型数据。**
**a.某些字符串可以转换成数字类型存储比如可以将 IP 地址转换成整型数据。**
数字是连续的,性能更好,占用空间也更小。
MySQL 提供了两个方法来处理 ip 地址
MySQL 提供了两个方法来处理 ip 地址
- `INET_ATON()`:把 ip 转为无符号整型 (4-8 位)
- `INET_NTOA()` :把整型的 ip 转为地址
- `INET_ATON()`:把 ip 转为无符号整型 (4-8 位)
- `INET_NTOA()`:把整型的 ip 转为地址。
插入数据前,先用 `INET_ATON()` 把 ip 地址转为整型显示数据时,使用 `INET_NTOA()` 把整型的 ip 地址转为地址显示即可。
插入数据前,先用 `INET_ATON()` 把 ip 地址转为整型显示数据时,使用 `INET_NTOA()` 把整型的 ip 地址转为地址显示即可。
**b.对于非负型的数据 (如自增 ID,整型 IP年龄) 来说,要优先使用无符号整型来存储。**
**b.对于非负型的数据 (如自增 ID、整型 IP、年龄) 来说,要优先使用无符号整型来存储。**
无符号相对于有符号可以多出一倍的存储空间
无符号相对于有符号可以多出一倍的存储空间
```sql
SIGNED INT -2147483648~2147483647
@ -103,7 +103,7 @@ UNSIGNED INT 0~4294967295
**c.小数值类型(比如年龄、状态表示如 0/1优先使用 TINYINT 类型。**
### 避免使用 TEXT,BLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据
### 避免使用 TEXTBLOB 数据类型,最常见的 TEXT 类型可以存储 64k 的数据
**a. 建议把 BLOB 或是 TEXT 列分离到单独的扩展表中。**
@ -113,21 +113,21 @@ MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型,如果查
**2、TEXT 或 BLOB 类型只能使用前缀索引**
因为 MySQL 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
因为 MySQL 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
### 避免使用 ENUM 类型
- 修改 ENUM 值需要使用 ALTER 语句
- ENUM 类型的 ORDER BY 操作效率低,需要额外操作
- ENUM 数据类型存在一些限制比如建议不要使用数值作为 ENUM 的枚举值。
- 修改 ENUM 值需要使用 ALTER 语句
- ENUM 类型的 ORDER BY 操作效率低,需要额外操作
- ENUM 数据类型存在一些限制比如建议不要使用数值作为 ENUM 的枚举值。
相关阅读:[是否推荐使用 MySQL 的 enum 类型? - 架构文摘 - 知乎](https://www.zhihu.com/question/404422255/answer/1661698499) 。
### 尽可能把所有列定义为 NOT NULL
除非有特别的原因使用 NULL 值,应该总是让字段保持 NOT NULL。
除非有特别的原因使用 NULL 值,否则应该总是让字段保持 NOT NULL。
- 索引 NULL 列需要额外的空间来保存,所以要占用更多的空间
- 索引 NULL 列需要额外的空间来保存,所以要占用更多的空间
- 进行比较和计算时要对 NULL 值做特别的处理。
相关阅读:[技术分享 | MySQL 默认值选型(是空,还是 NULL](https://opensource.actionsky.com/20190710-mysql/) 。
@ -136,7 +136,7 @@ MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型,如果查
对于日期类型来说,一定不要用字符串存储日期。可以考虑 DATETIME、TIMESTAMP 和数值型时间戳。
这三种种方式都有各自的优势,根据实际场景选择最合适的才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型:
这三种种方式都有各自的优势,根据实际场景选择最合适的才是王道。下面再对这三种方式做一个简单的对比,以供大家实际开发中选择正确的存放时间的数据类型:
| 类型 | 存储空间 | 日期格式 | 日期范围 | 是否带时区信息 |
| ------------ | -------- | ------------------------------ | ------------------------------------------------------------ | -------------- |
@ -148,10 +148,10 @@ MySQL 时间类型选择的详细介绍请看这篇:[MySQL 时间类型数据
### 同财务相关的金额类数据必须使用 decimal 类型
- **非精准浮点**float,double
- **非精准浮点**floatdouble
- **精准浮点**decimal
decimal 类型为精准浮点数,在计算时不会丢失精度。占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字并且小数点要占用一个字节。并且decimal 可用于存储比 bigint 更大的整型数据
decimal 类型为精准浮点数,在计算时不会丢失精度。占用空间由定义的宽度决定,每 4 个字节可以存储 9 位数字并且小数点要占用一个字节。并且decimal 可用于存储比 bigint 更大的整型数据
不过, 由于 decimal 需要额外的空间和计算开销,应该尽量只在需要对数据进行精确计算时才使用 decimal 。
@ -161,13 +161,13 @@ decimal 类型为精准浮点数,在计算时不会丢失精度。占用空间
## 索引设计规范
### 限制每张表上的索引数量,建议单张表索引不超过 5 个
### 限制每张表上的索引数量建议单张表索引不超过 5 个
索引并不是越多越好!索引可以提高效率同样可以降低效率。
索引并不是越多越好!索引可以提高效率同样可以降低效率。
索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。
因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
因为 MySQL 优化器在选择如何优化查询时,会根据统一信息,对每一个可以用到的索引来进行评估,以生成出一个最好的执行计划如果同时有很多个索引都可以用于查询,就会增加 MySQL 优化器生成执行计划的时间,同样会降低查询性能。
### 禁止使用全文索引
@ -175,46 +175,46 @@ decimal 类型为精准浮点数,在计算时不会丢失精度。占用空间
### 禁止给表中的每一列都建立单独的索引
5.6 版本之前,一个 sql 只能使用到一个表中的一个索引5.6 以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好。
5.6 版本之前,一个 sql 只能使用到一个表中的一个索引5.6 以后,虽然有了合并索引的优化方式,但是还是远远没有使用一个联合索引的查询方式好。
### 每个 InnoDB 表必须有个主键
InnoDB 是一种索引组织表:数据的存储的逻辑顺序和索引的顺序是相同的。每个表都可以有多个索引,但是表的存储顺序只能有一种。
InnoDB 是按照主键索引的顺序来组织表的
InnoDB 是按照主键索引的顺序来组织表的
- 不要使用更新频繁的列作为主键,不使用多列主键(相当于联合索引)
- 不要使用 UUID,MD5,HASH,字符串列作为主键(无法保证数据的顺序增长)
- 主键建议使用自增 ID 值
- 不要使用更新频繁的列作为主键,不使用多列主键(相当于联合索引)
- 不要使用 UUID、MD5、HASH、字符串列作为主键(无法保证数据的顺序增长)
- 主键建议使用自增 ID 值
### 常见索引列建议
- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列
- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段
- 不要将符合 1 和 2 中的字段的列都建立一个索引, 通常将 1、2 中的字段建立联合索引效果更好
- 多表 join 的关联列
- 出现在 SELECT、UPDATE、DELETE 语句的 WHERE 从句中的列
- 包含在 ORDER BY、GROUP BY、DISTINCT 中的字段
- 不要将符合 1 和 2 中的字段的列都建立一个索引,通常将 1、2 中的字段建立联合索引效果更好
- 多表 join 的关联列
### 如何选择索引列的顺序
建立索引的目的是:希望通过索引进行数据查找,减少随机 IO增加查询性能索引能过滤出越少的数据则从磁盘中读入的数据也就越少。
- **区分度最高的列放在联合索引的最左侧:** 这是最重要的原则。区分度越高通过索引筛选出的数据就越少I/O 操作也就越少。计算区分度的方法是 `count(distinct column) / count(*)`
- **最频繁使用的列放在联合索引的左侧:** 这符合最左前缀匹配原则。将最常用的查询条件列放在最左侧,可以最大程度地利用索引。
- **字段长度:** 字段长度对联合索引非叶子节点的影响很小,因为它存储了所有联合索引字段的值。字段长度主要影响主键和包含在其他索引中的字段的存储空间,以及这些索引的叶子节点的大小。因此,在选择联合索引列的顺序时,字段长度的优先级最低。 对于主键和包含在其他索引中的字段,选择较短的字段长度可以节省存储空间和提高 I/O 性能。
- **区分度最高的列放在联合索引的最左侧**这是最重要的原则。区分度越高通过索引筛选出的数据就越少I/O 操作也就越少。计算区分度的方法是 `count(distinct column) / count(*)`
- **最频繁使用的列放在联合索引的左侧**这符合最左前缀匹配原则。将最常用的查询条件列放在最左侧,可以最大程度地利用索引。
- **字段长度**字段长度对联合索引非叶子节点的影响很小,因为它存储了所有联合索引字段的值。字段长度主要影响主键和包含在其他索引中的字段的存储空间,以及这些索引的叶子节点的大小。因此,在选择联合索引列的顺序时,字段长度的优先级最低。对于主键和包含在其他索引中的字段,选择较短的字段长度可以节省存储空间和提高 I/O 性能。
### 避免建立冗余索引和重复索引(增加了查询优化器生成执行计划的时间)
- 重复索引示例primary key(id)、index(id)、unique index(id)
- 冗余索引示例index(a,b,c)、index(a,b)、index(a)
- 重复索引示例primary key(id)、index(id)、unique index(id)
- 冗余索引示例index(a,b,c)、index(a,b)、index(a)
### 对于频繁的查询优先考虑使用覆盖索引
### 对于频繁的查询优先考虑使用覆盖索引
> 覆盖索引:就是包含了所有查询字段 (where,select,order by,group by 包含的字段) 的索引
> 覆盖索引:就是包含了所有查询字段 (where、select、order by、group by 包含的字段) 的索引
**覆盖索引的好处**
**覆盖索引的好处**
- **避免 InnoDB 表进行索引的二次查询,也就是回表操作:** InnoDB 是以聚集索引的顺序来存储的,对于 InnoDB 来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询(回表),减少了 IO 操作,提升了查询效率。
- **可以把随机 IO 变成顺序 IO 加快查询效率:** 由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,对比随机从磁盘读取每一行的数据 IO 要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的 IO 转变成索引查找的顺序 IO。
- **避免 InnoDB 表进行索引的二次查询,也就是回表操作**InnoDB 是以聚集索引的顺序来存储的,对于 InnoDB 来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询(回表),减少了 IO 操作,提升了查询效率。
- **可以把随机 IO 变成顺序 IO 加快查询效率**由于覆盖索引是按键值的顺序存储的,对于 IO 密集型的范围查找来说,对比随机从磁盘读取每一行的数据 IO 要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的 IO 转变成索引查找的顺序 IO。
---
@ -222,9 +222,9 @@ InnoDB 是按照主键索引的顺序来组织表的
**尽量避免使用外键约束**
- 不建议使用外键约束foreign key但一定要在表与表之间的关联键上建立索引
- 外键可用于保证数据的参照完整性,但建议在业务端实现
- 外键会影响父表和子表的写操作从而降低性能
- 不建议使用外键约束foreign key但一定要在表与表之间的关联键上建立索引
- 外键可用于保证数据的参照完整性,但建议在业务端实现
- 外键会影响父表和子表的写操作从而降低性能
## 数据库 SQL 开发规范
@ -238,7 +238,7 @@ InnoDB 是按照主键索引的顺序来组织表的
### 充分利用表上已经存在的索引
避免使用双%号的查询条件。如:`a like '%123%'`(如果无前置%,只有后置%,是可以用到列上的索引的)
避免使用双%号的查询条件。如:`a like '%123%'`(如果无前置%,只有后置%,是可以用到列上的索引的)
一个 SQL 只能利用到复合索引中的一列进行范围查询。如:有 a,b,c 列的联合索引,在查询条件中有 a 列的范围查询,则在 b,c 列上的索引将不会被用到。
@ -248,18 +248,18 @@ InnoDB 是按照主键索引的顺序来组织表的
- `SELECT *` 会消耗更多的 CPU。
- `SELECT *` 无用字段增加网络带宽资源消耗,增加数据传输时间,尤其是大字段(如 varchar、blob、text
- `SELECT *` 无法使用 MySQL 优化器覆盖索引的优化(基于 MySQL 优化器的“覆盖索引”策略又是速度极快,效率极高,业界极为推荐的查询优化方式)
- `SELECT <字段列表>` 可减少表结构变更带来的影响
- `SELECT *` 无法使用 MySQL 优化器覆盖索引的优化(基于 MySQL 优化器的“覆盖索引”策略又是速度极快、效率极高、业界极为推荐的查询优化方式)。
- `SELECT <字段列表>` 可减少表结构变更带来的影响
### 禁止使用不含字段列表的 INSERT 语句
**不推荐**
```sql
insert into t values ('a','b','c');
```
应使用
**推荐**
```sql
insert into t(c1,c2,c3) values ('a','b','c');
@ -273,7 +273,7 @@ insert into t(c1,c2,c3) values ('a','b','c');
### 避免数据类型的隐式转换
隐式转换会导致索引失效如:
隐式转换会导致索引失效,如:
```sql
select name,phone from customer where id = '111';
@ -283,9 +283,9 @@ select name,phone from customer where id = '111';
### 避免使用子查询,可以把子查询优化为 join 操作
通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时,才可以把子查询转化为关联查询进行优化。
通常子查询在 in 子句中,且子查询中为简单 SQL(不包含 union、group by、order by、limit 从句) 时才可以把子查询转化为关联查询进行优化。
**子查询性能差的原因** 子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。
**子查询性能差的原因**子查询的结果集无法使用索引,通常子查询的结果集会被存储到临时表中,不论是内存临时表还是磁盘临时表都不会存在索引,所以查询性能会受到一定的影响。特别是对于返回结果集比较大的子查询,其对查询性能的影响也就越大。由于子查询会产生大量的临时表也没有索引,所以会消耗过多的 CPU 和 IO 资源,产生大量的慢查询。
### 避免使用 JOIN 关联太多的表
@ -293,7 +293,7 @@ select name,phone from customer where id = '111';
在 MySQL 中,对于同一个 SQL 多关联join一个表就会多分配一个关联缓存如果在一个 SQL 中关联的表越多,所占用的内存也就越大。
如果程序中大量的使用了多表关联的操作,同时 join_buffer_size 设置的也不合理的情况下,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。
如果程序中大量地使用了多表关联的操作,同时 join_buffer_size 设置得也不合理,就容易造成服务器内存溢出的情况,就会影响到服务器数据库性能的稳定性。
同时对于关联操作来说会产生临时表操作影响查询效率MySQL 最多允许关联 61 个表,建议不超过 5 个。
@ -303,25 +303,25 @@ select name,phone from customer where id = '111';
### 对应同一列进行 or 判断时,使用 in 代替 or
in 的值不要超过 500 个in 操作可以更有效的利用索引or 大多数情况下很少能利用到索引。
in 的值不要超过 500 个in 操作可以更有效的利用索引or 大多数情况下很少能利用到索引。
### 禁止使用 order by rand() 进行随机排序
order by rand() 会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值如果满足条件的数据集非常大,就会消耗大量的 CPU 和 IO 及内存资源。
order by rand() 会把表中所有符合条件的数据装载到内存中,然后在内存中对所有数据根据随机生成的值进行排序,并且可能会对每一行都生成一个随机值如果满足条件的数据集非常大,就会消耗大量的 CPU 和 IO 及内存资源。
推荐在程序中获取一个随机值,然后从数据库中获取数据的方式。
### WHERE 从句中禁止对列进行函数转换和计算
对列进行函数转换或计算时会导致无法使用索引
对列进行函数转换或计算时会导致无法使用索引
**不推荐**
**不推荐**
```sql
where date(create_time)='20190101'
```
**推荐**
**推荐**
```sql
where create_time >= '20190101' and create_time < '20190102'
@ -329,43 +329,43 @@ where create_time >= '20190101' and create_time < '20190102'
### 在明显不会有重复值时使用 UNION ALL 而不是 UNION
- UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作
- UNION ALL 不会再对结果集进行去重操作
- UNION 会把两个结果集的所有数据放到临时表中后再进行去重操作
- UNION ALL 不会再对结果集进行去重操作
### 拆分复杂的大 SQL 为多个小 SQL
- 大 SQL 逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL
- MySQL 中,一个 SQL 只能使用一个 CPU 进行计算
- SQL 拆分后可以通过并行执行来提高处理效率
- 大 SQL 逻辑上比较复杂,需要占用大量 CPU 进行计算的 SQL
- MySQL 中,一个 SQL 只能使用一个 CPU 进行计算
- SQL 拆分后可以通过并行执行来提高处理效率
### 程序连接不同的数据库使用不同的账号,禁止跨库查询
- 为数据库迁移和分库分表留出余地
- 降低业务耦合度
- 避免权限过大而产生的安全风险
- 为数据库迁移和分库分表留出余地
- 降低业务耦合度
- 避免权限过大而产生的安全风险
## 数据库操作行为规范
### 超 100 万行的批量写 (UPDATE,DELETE,INSERT) 操作,要分批多次进行操作
### 超 100 万行的批量写 (UPDATE、DELETE、INSERT) 操作,要分批多次进行操作
**大批量操作可能会造成严重的主从延迟**
主从环境中,大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况
主从环境中大批量操作可能会造成严重的主从延迟,大批量的写操作一般都需要执行一定长的时间,而只有当主库上执行完成后,才会在其他从库上执行,所以会造成主库与从库长时间的延迟情况
**binlog 日志为 row 格式时会产生大量的日志**
大批量写操作会产生大量日志,特别是对于 row 格式二进制数据而言,由于在 row 格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因
大批量写操作会产生大量日志,特别是对于 row 格式二进制数据而言,由于在 row 格式中会记录每一行数据的修改,我们一次修改的数据越多,产生的日志量也就会越多,日志的传输和恢复所需要的时间也就越长,这也是造成主从延迟的一个原因
**避免产生大事务操作**
大批量修改数据,一定是在一个事务中进行的,这就会造成表中大批量数据进行锁定,从而导致大量的阻塞,阻塞会对 MySQL 的性能产生非常大的影响。
特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批
特别是长时间的阻塞会占满所有数据库的可用连接,这会使生产环境中的其他应用无法连接到数据库,因此一定要注意大批量写操作要进行分批
### 对于大表使用 pt-online-schema-change 修改表结构
- 避免大表修改产生的主从延迟
- 避免在对表字段进行修改时进行锁表
- 避免大表修改产生的主从延迟
- 避免在对表字段进行修改时进行锁表
对大表数据结构的修改一定要谨慎,会造成严重的锁表操作,尤其是生产环境,是不能容忍的。
@ -373,13 +373,13 @@ pt-online-schema-change 它会首先建立一个与原表结构相同的新表
### 禁止为程序使用的账号赋予 super 权限
- 当达到最大连接数限制时,还运行 1 个有 super 权限的用户连接
- super 权限只能留给 DBA 处理问题的账号使用
- 当达到最大连接数限制时,还运行 1 个有 super 权限的用户连接
- super 权限只能留给 DBA 处理问题的账号使用
### 对于程序连接数据库账号,遵循权限最小原则
### 对于程序连接数据库账号遵循权限最小原则
- 程序使用数据库账号只能在一个 DB 下使用,不准跨库
- 程序使用的账号原则上不准有 drop 权限
- 程序使用数据库账号只能在一个 DB 下使用,不准跨库
- 程序使用的账号原则上不准有 drop 权限
## 推荐阅读

View File

@ -15,20 +15,20 @@ tag:
**索引是一种用于快速查询和检索数据的数据结构,其本质可以看成是一种排序好的数据结构。**
索引的作用就相当于书的目录。打个比方: 我们在查字典的时候,如果没有目录,那我们就只能一页一页的去找我们需要查的那个字,速度很慢。如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引的作用就相当于书的目录。打个比方:我们在查字典的时候,如果没有目录,那我们就只能一页一页地去找我们需要查的那个字,速度很慢;如果有目录了,我们只需要先去目录里查找字的位置,然后直接翻到那一页就行了。
索引底层数据结构存在很多种类型,常见的索引结构有: B 树, B+树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyIsam都使用了 B+树作为索引结构。
索引底层数据结构存在很多种类型,常见的索引结构有B 树、 B+ 树 和 Hash、红黑树。在 MySQL 中,无论是 Innodb 还是 MyISAM都使用了 B+ 树作为索引结构。
## 索引的优缺点
**优点**
- 使用索引可以大大加快数据的检索速度(大大减少检索的数据量), 减少 IO 次数,这也是创建索引的最主要的原因。
- 使用索引可以大大加快数据的检索速度(大大减少检索的数据量)减少 IO 次数,这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
**缺点**
- 创建索引和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态的修改,会降低 SQL 执行效率。
- 创建和维护索引需要耗费许多时间。当对表中的数据进行增删改的时候,如果数据有索引,那么索引也需要动态地修改,这会降低 SQL 执行效率。
- 索引需要使用物理文件存储,也会耗费一定空间。
但是,**使用索引一定能提高查询性能吗?**
@ -39,7 +39,7 @@ tag:
### Hash 表
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O1)。
哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
**为何能够通过 key 快速取出 value 呢?** 原因在于 **哈希算法**(也叫散列算法)。通过哈希算法,我们可以快速找到 key 对应的 index找到了 index 也就找到了对应的 value。
@ -50,7 +50,7 @@ index = hash % array_size
![](https://oss.javaguide.cn/github/javaguide/database/mysql20210513092328171.png)
但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过JDK1.8 以后`HashMap`为了减少链表过长的时候搜索时间过长引入了红黑树。
但是!哈希算法有个 **Hash 冲突** 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 **链地址法**。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 `HashMap` 就是通过链地址法来解决哈希冲突的。不过JDK1.8 以后`HashMap`为了提高链表过长时的搜索效率,引入了红黑树。
![](https://oss.javaguide.cn/github/javaguide/database/mysql20210513092224836.png)
@ -60,15 +60,15 @@ MySQL 的 InnoDB 存储引擎不直接支持常规的哈希索引但是Inn
既然哈希表这么快,**为什么 MySQL 没有使用其作为索引的数据结构呢?** 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。
试想一种情况:
试想一种情况
```java
SELECT * FROM tb1 WHERE id < 500;
```
在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗?这就是 Hash 最大的缺点了。
在这种范围查询中,优势非常大,直接遍历比 500 小的叶子节点就够了。而 Hash 索引是根据 hash 算法来定位的,难不成还要把 1 - 499 的数据,每个都进行一次 hash 计算来定位吗这就是 Hash 最大的缺点了。
### 二叉查找树(BST)
### 二叉查找树BST
二叉查找树Binary Search Tree是一种基于二叉树的数据结构它具有以下特点
@ -76,7 +76,7 @@ SELECT * FROM tb1 WHERE id < 500;
2. 右子树所有节点的值均大于根节点的值。
3. 左右子树也分别为二叉查找树。
当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂退化为 ON
当二叉查找树是平衡的时候,也就是树的每个节点的左右子树深度相差不超过 1 的时候,查询的时间复杂度为 O(log2(N)),具有比较高的效率。然而,当二叉查找树不平衡时,例如在最坏情况下(有序插入节点),树会退化成线性链表(也被称为斜树),导致查询效率急剧下降,时间复杂退化为 O(N)
![斜树](https://oss.javaguide.cn/github/javaguide/cs-basics/data-structure/oblique-tree.png)
@ -114,14 +114,14 @@ AVL 树采用了旋转操作来保持平衡。主要有四种旋转操作LL
### B 树& B+ 树
B 树也称 B-树,全称为 **多路平衡查找树** B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 `Balanced` (平衡)的意思。
B 树也称 B- 树,全称为 **多路平衡查找树**B+ 树是 B 树的一种变体。B 树和 B+ 树中的 B 是 `Balanced`(平衡)的意思。
目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
**B 树& B+ 树两者有何异同呢?**
- B 树的所有节点既存放键(key)也存放数据(data),而 B+ 树只有叶子节点存放 key 和 data其他内节点只存放 key。
- B 树的叶子节点都是独立的;B+树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的叶子节点都是独立的B+ 树的叶子节点有一条引用链指向与它相邻的叶子节点。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+ 树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程,叶子节点的顺序检索很明显。
- 在 B 树中进行范围查询时,首先找到要查找的下限,然后对 B 树进行中序遍历,直到找到查找的上限;而 B+ 树的范围查询,只需要对链表进行遍历即可。
@ -140,12 +140,12 @@ B 树也称 B-树,全称为 **多路平衡查找树** B+ 树是 B 树的一
- BTree 索引MySQL 里默认和最常用的索引类型。只有叶子节点存储 value非叶子节点只有指针和 key。存储引擎 MyISAM 和 InnoDB 实现 BTree 索引都是使用 B+Tree但二者实现方式不一样前面已经介绍了
- 哈希索引:类似键值对的形式,一次即可定位。
- RTree 索引:一般不会使用,仅支持 geometry 数据类型,优势在于范围查找,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR` `TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR``TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
按照底层存储方式角度划分:
- 聚簇索引聚集索引索引结构和数据一起存放的索引InnoDB 中的主键索引就属于聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
- 非聚簇索引(非聚集索引):索引结构和数据分开存放的索引,二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
按照应用维度划分:
@ -154,7 +154,7 @@ B 树也称 B-树,全称为 **多路平衡查找树** B+ 树是 B 树的一
- 唯一索引:加速查询 + 列值唯一(可以有 NULL
- 覆盖索引:一个索引包含(或者说覆盖)所有需要查询的字段的值。
- 联合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR` `TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 全文索引:对文本的内容进行分词,进行搜索。目前只有 `CHAR``VARCHAR``TEXT` 列上可以创建全文索引。一般不会使用,效率较低,通常使用搜索引擎如 ElasticSearch 代替。
- 前缀索引:对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
MySQL 8.x 中实现的索引新特性:
@ -163,7 +163,7 @@ MySQL 8.x 中实现的索引新特性:
- 降序索引:之前的版本就支持通过 desc 来指定索引为降序,但实际上创建的仍然是常规的升序索引。直到 MySQL 8.x 版本才开始真正支持降序索引。另外,在 MySQL 8.x 版本中,不再对 GROUP BY 语句进行隐式排序。
- 函数索引:从 MySQL 8.0.13 版本开始支持在索引中使用函数或者表达式的值,也就是在索引中可以包含函数或者表达式。
## 主键索引(Primary Key)
## 主键索引Primary Key
数据表的主键列使用的就是主键索引。
@ -177,16 +177,16 @@ MySQL 8.x 中实现的索引新特性:
二级索引Secondary Index的叶子节点存储的数据是主键的值也就是说通过二级索引可以定位主键的位置二级索引又称为辅助索引/非主键索引。
唯一索引,普通索引,前缀索引等索引都属于二级索引。
唯一索引、普通索引、前缀索引等索引都属于二级索引。
PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
PS不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,也可以自行搜索。
1. **唯一索引(Unique Key)**:唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
2. **普通索引(Index)**:普通索引的唯一作用就是为了快速查询数据,一张表允许创建多个普通索引,并允许数据重复和 NULL。
3. **前缀索引(Prefix)**:前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
4. **全文索引(Full Text)**:全文索引主要是为了检索大文本数据中的关键字的信息是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引5.6 之后 InnoDB 也支持了全文索引。
1. **唯一索引Unique Key**唯一索引也是一种约束。唯一索引的属性列不能出现重复的数据,但是允许数据为 NULL一张表允许创建多个唯一索引。 建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
2. **普通索引Index**:普通索引的唯一作用就是为了快速查询数据。一张表允许创建多个普通索引,并允许数据重复和 NULL。
3. **前缀索引Prefix**前缀索引只适用于字符串类型的数据。前缀索引是对文本的前几个字符创建索引,相比普通索引建立的数据更小,因为只取前几个字符。
4. **全文索引Full Text**全文索引主要是为了检索大文本数据中的关键字的信息是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MyISAM 引擎支持全文索引5.6 之后 InnoDB 也支持了全文索引。
二级索引:
二级索引
![二级索引](https://oss.javaguide.cn/github/javaguide/open-source-project/no-cluster-index.png)
@ -198,7 +198,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
聚簇索引Clustered Index即索引结构和数据一起存放的索引并不是一种单独的索引类型。InnoDB 中的主键索引就属于聚簇索引。
在 MySQL 中InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引(B+树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
在 MySQL 中InnoDB 引擎的表的 `.ibd`文件就包含了该表的索引和数据,对于 InnoDB 引擎表来说,该表的索引B+ 树)的每个非叶子节点存储索引,叶子节点存储索引和索引对应的数据。
#### 聚簇索引的优缺点
@ -216,7 +216,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
#### 非聚簇索引介绍
非聚簇索引(Non-Clustered Index)即索引结构和数据分开存放的索引,并不是一种单独的索引类型。二级索引(辅助索引)就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引Non-Clustered Index即索引结构和数据分开存放的索引并不是一种单独的索引类型。二级索引辅助索引就属于非聚簇索引。MySQL 的 MyISAM 引擎,不管主键还是非主键,使用的都是非聚簇索引。
非聚簇索引的叶子节点并不一定存放数据的指针,因为二级索引的叶子节点就存放的是主键,根据主键再回表查数据。
@ -228,18 +228,18 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
**缺点**
- **依赖于有序的数据**:跟聚簇索引一样,非聚簇索引也依赖于有序的数据
- **可能会二次查询(回表)**:这应该是非聚簇索引最大的缺点了。 当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
- **依赖于有序的数据**跟聚簇索引一样,非聚簇索引也依赖于有序的数据
- **可能会二次查询(回表)**:这应该是非聚簇索引最大的缺点了。当查到索引对应的指针或主键后,可能还需要根据指针或主键再到数据文件或表中查询。
这是 MySQL 的表的文件截图:
这是 MySQL 的表的文件截图
![MySQL 表的文件](https://oss.javaguide.cn/github/javaguide/database/mysql20210420165311654.png)
聚簇索引和非聚簇索引:
聚簇索引和非聚簇索引
![聚簇索引和非聚簇索引](https://oss.javaguide.cn/github/javaguide/database/mysql20210420165326946.png)
#### 非聚簇索引一定回表查询吗(覆盖索引)?
#### 非聚簇索引一定回表查询吗(覆盖索引)?
**非聚簇索引不一定回表查询。**
@ -251,7 +251,7 @@ PS: 不懂的同学可以暂存疑,慢慢往下看,后面会有答案的,
那么这个索引的 key 本身就是 name查到对应的 name 直接返回就行了,无需回表查询。
即使是 MYISAM 也是这样,虽然 MYISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!**如果 SQL 查的就是主键呢?**
即使是 MyISAM 也是这样,虽然 MyISAM 的主键索引确实需要回表,因为它的主键索引的叶子节点存放的是指针。但是!**如果 SQL 查的就是主键呢?**
```sql
SELECT id FROM table WHERE id=1;
@ -400,7 +400,7 @@ MySQL 8.0.13 版本引入了索引跳跃扫描Index Skip Scan简称 ISS
**索引下推Index Condition Pushdown简称 ICP** 是 **MySQL 5.6** 版本中提供的一项索引优化功能,它允许存储引擎在索引遍历过程中,执行部分 `WHERE` 字句的判断条件,直接过滤掉不满足条件的记录,从而减少回表次数,提高查询效率。
假设我们有一个名为 `user` 的表,其中包含 `id`, `username`, `zipcode``birthdate` 4 个字段,创建了联合索引`(zipcode, birthdate)`
假设我们有一个名为 `user` 的表,其中包含 `id``username``zipcode` `birthdate` 4 个字段,创建了联合索引 `(zipcode, birthdate)`
```sql
CREATE TABLE `user` (
@ -449,7 +449,7 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
最后,总结一下索引下推应用范围:
1. 适用于 InnoDB 引擎和 MyISAM 引擎的查询。
2. 适用于执行计划是 range, ref, eq_ref, ref_or_null 的范围查询。
2. 适用于执行计划是 range、ref、eq_ref、ref_or_null 的范围查询。
3. 对于 InnoDB 表,仅用于非聚簇索引。索引下推的目标是减少全行读取次数,从而减少 I/O 操作。对于 InnoDB 聚集索引,完整的记录已经读入 InnoDB 缓冲区。在这种情况下使用索引下推不会减少 I/O。
4. 子查询不能使用索引下推,因为子查询通常会创建临时表来处理结果,而这些临时表是没有索引的。
5. 存储过程不能使用索引下推,因为存储引擎无法调用存储函数。
@ -458,7 +458,7 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
### 选择合适的字段创建索引
- **不为 NULL 的字段**:索引字段的数据应该尽量不为 NULL因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL建议使用 0,1,true,false 这样语义较为清晰的短值或短字符作为替代。
- **不为 NULL 的字段**:索引字段的数据应该尽量不为 NULL因为对于数据为 NULL 的字段,数据库较难优化。如果字段频繁被查询,但又避免不了为 NULL建议使用 0、1、true、false 这样语义较为清晰的短值或短字符作为替代。
- **被频繁查询的字段**:我们创建索引的字段应该是查询操作非常频繁的字段。
- **被作为条件查询的字段**:被作为 WHERE 条件查询的字段,应该被考虑建立索引。
- **频繁需要排序的字段**:索引已经排序,这样查询可以利用索引的排序,加快排序查询时间。
@ -470,7 +470,7 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
### 限制每张表上的索引数量
索引并不是越多越好,建议单张表索引不超过 5 个!索引可以提高效率同样可以降低效率。
索引并不是越多越好,建议单张表索引不超过 5 个!索引可以提高效率同样可以降低效率。
索引可以增加查询效率,但同样也会降低插入和更新的效率,甚至有些情况下会降低查询效率。
@ -482,7 +482,7 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
### 注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如name,city name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city)和(name)这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的。在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
### 字符串类型的字段使用前缀索引代替普通索引
@ -492,13 +492,13 @@ MySQL 可以简单分为 Server 层和存储引擎层这两层。Server 层处
索引失效也是慢查询的主要原因之一,常见的导致索引失效的情况有下面这些:
- ~~使用 `SELECT *` 进行查询;~~ `SELECT *` 不会直接导致索引失效(如果不走索引大概率是因为 where 查询范围过大导致的),但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖;
- 创建了组合索引,但查询条件未遵守最左匹配原则;
- 在索引列上进行计算、函数、类型转换等操作;
- 以 % 开头的 LIKE 查询比如 `LIKE '%abc';`;
- 查询条件中使用 OR且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到;
- IN 的取值范围较大时会导致索引失效,走全表扫描(NOT IN 和 IN 的失效场景相同);
- 发生[隐式转换](https://javaguide.cn/database/mysql/index-invalidation-caused-by-implicit-conversion.html);
- ~~使用 `SELECT *` 进行查询;~~ `SELECT *` 不会直接导致索引失效(如果不走索引大概率是因为 where 查询范围过大导致的),但它可能会带来一些其他的性能问题比如造成网络传输和数据处理的浪费、无法使用索引覆盖
- 创建了组合索引,但查询条件未遵守最左匹配原则
- 在索引列上进行计算、函数、类型转换等操作
- 以 % 开头的 LIKE 查询比如 `LIKE '%abc';`
- 查询条件中使用 OR且 OR 的前后条件中有一个列没有索引,涉及的索引都不会被使用到
- IN 的取值范围较大时会导致索引失效,走全表扫描NOT IN 和 IN 的失效场景相同);
- 发生[隐式转换](https://javaguide.cn/database/mysql/index-invalidation-caused-by-implicit-conversion.html)
- ……
推荐阅读这篇文章:[美团暑期实习一面MySQl 索引失效的场景有哪些?](https://mp.weixin.qq.com/s/mwME3qukHBFul57WQLkOYg)。

View File

@ -34,18 +34,18 @@ Redis 没有外部依赖Linux 和 OS X 是 Redis 开发和测试最多的两
### Redis 为什么这么快?
Redis 内部做了非常多的性能优化,比较重要的有下面 3 点:
Redis 内部做了非常多的性能优化,比较重要的有下面 4 点:
1. Redis 基于内存,内存的访问速度比磁盘快很多;
2. Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型,主要是单线程事件循环和 IO 多路复用Redis 线程模式后面会详细介绍到);
3. Redis 内置了多种优化过后的数据类型/结构实现,性能非常高
3. Redis 内置了多种优化过后的数据类型/结构实现,性能非常高
4. Redis 通信协议实现简单且解析高效。
> 下面这张图片总结的挺不错的,分享一下,出自 [Why is Redis so fast?](https://twitter.com/alexxubyte/status/1498703822528544770)。
![why-redis-so-fast](./images/why-redis-so-fast.png)
那既然都这么快了,为什么不直接用 Redis 当主数据库呢?主要是因为内存成本太高且 Redis 提供的数据持久化仍然有数据丢失的风险。
那既然都这么快了,为什么不直接用 Redis 当主数据库呢?主要是因为内存成本太高,并且 Redis 提供的数据持久化仍然有数据丢失的风险。
### 除了 Redis你还知道其他分布式缓存方案吗
@ -71,7 +71,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
- [Dragonfly](https://github.com/dragonflydb/dragonfly):一种针对现代应用程序负荷需求而构建的内存数据库,完全兼容 Redis 和 Memcached 的 API迁移时无需修改任何代码号称全世界最快的内存数据库。
- [KeyDB](https://github.com/Snapchat/KeyDB)Redis 的一个高性能分支,专注于多线程、内存效率和高吞吐量。
不过,个人还是建议分布式缓存首选 Redis ,毕竟经过这么多年的生考验,生态也这么优秀,资料也很全面!
不过,个人还是建议分布式缓存首选 Redis,毕竟经过了这么多年的考验,生态非常优秀,资料也很全面!
PS篇幅问题我这并没有对上面提到的分布式缓存选型做详细介绍和对比感兴趣的话可以自行研究一下。
@ -87,10 +87,10 @@ PS篇幅问题我这并没有对上面提到的分布式缓存选型做详
**区别**
1. **数据类型**Redis 支持更丰富的数据类型支持更复杂的应用场景。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 listsetzsethash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
2. **数据持久化**Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用而 Memcached 把数据全部存在内存之中。也就是说Redis 有灾难恢复机制而 Memcached 没有。
3. **集群模式支持**Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 自 3.0 版本起是原生支持集群模式的。
4. **线程模型**Memcached 是多线程,非阻塞 IO 复用的网络模型Redis 使用单线程的多路 IO 复用模型。 Redis 6.0 针对网络数据的读写引入了多线程)
1. **数据类型**Redis 支持更丰富的数据类型支持更复杂的应用场景。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list、set、zset、hash 等数据结构的存储;而Memcached 只支持最简单的 k/v 数据类型。
2. **数据持久化**Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用而 Memcached 把数据全部存在内存之中。也就是说Redis 有灾难恢复机制而 Memcached 没有。
3. **集群模式支持**Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据; Redis 自 3.0 版本起是原生支持集群模式的。
4. **线程模型**Memcached 是多线程、非阻塞 IO 复用的网络模型;而 Redis 使用单线程的多路 IO 复用模型Redis 6.0 针对网络数据的读写引入了多线程)
5. **特性支持**Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且Redis 支持更多的编程语言。
6. **过期数据删除**Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
@ -156,7 +156,7 @@ Redis 从 4.0 版本开始,支持通过 Module 来扩展其功能以满足特
- **消息队列**Redis 自带的 List 数据结构可以作为一个简单的队列使用。Redis 5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka有主题和消费组的概念支持消息持久化以及 ACK 机制。
- **延时队列**Redisson 内置了延时队列(基于 Sorted Set 实现的)。
- **分布式 Session**:利用 String 或者 Hash 数据类型保存 Session 数据,所有的服务器都可以访问。
- **复杂业务场景**:通过 Redis 以及 Redis 扩展(比如 Redisson提供的数据结构我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜、通过 HyperLogLog 统计网站 UV 和 PV。
- **复杂业务场景**:通过 Redis 以及 Redis 扩展(比如 Redisson提供的数据结构我们可以很方便地完成很多复杂的业务场景比如通过 Bitmap 统计活跃用户、通过 Sorted Set 维护排行榜、通过 HyperLogLog 统计网站 UV 和 PV。
- ……
### 如何基于 Redis 实现分布式锁?
@ -216,11 +216,11 @@ pub/sub 既能单播又能广播,还支持 channel 的简单正则匹配。不
为此Redis 5.0 新增加的一个数据结构 `Stream` 来做消息队列。`Stream` 支持:
- 发布 / 订阅模式
- 按照消费者组进行消费(借鉴了 Kafka 消费者组的概念)
- 消息持久化( RDB 和 AOF
- ACK 机制(通过确认机制来告知已经成功处理了消息)
- 阻塞式获取消息
- 发布 / 订阅模式
- 按照消费者组进行消费(借鉴了 Kafka 消费者组的概念)
- 消息持久化( RDB 和 AOF
- ACK 机制(通过确认机制来告知已经成功处理了消息)
- 阻塞式获取消息
`Stream` 的结构如下:
@ -230,7 +230,7 @@ pub/sub 既能单播又能广播,还支持 channel 的简单正则匹配。不
这里再对图中涉及到的一些概念,进行简单解释:
- `Consumer Group`:消费者组用于组织和管理多个消费者。消费者组本身不处理消息,而是再将消息分发给消费者,由消费者进行真正的消费
- `Consumer Group`:消费者组用于组织和管理多个消费者。消费者组本身不处理消息,而是再将消息分发给消费者,由消费者进行真正的消费
- `last_delivered_id`:标识消费者组当前消费位置的游标,消费者组中任意一个消费者读取了消息都会使 last_delivered_id 往前移动。
- `pending_ids`:记录已经被客户端消费但没有 ack 的消息的 ID。
@ -245,19 +245,19 @@ pub/sub 既能单播又能广播,还支持 channel 的简单正则匹配。不
- `XTRIM`:修剪流的长度,可以指定修建策略(`MAXLEN`/`MINID`)。
- `XLEN`:获取流的长度。
- `XGROUP CREATE`:创建消费者组。
- `XGROUP DESTROY` 删除消费者组
- `XGROUP DESTROY`:删除消费者组
- `XGROUP DELCONSUMER`:从消费者组中删除一个消费者。
- `XGROUP SETID`:为消费者组设置新的最后递送消息 ID
- `XGROUP SETID`:为消费者组设置新的最后递送消息 ID
- `XACK`:确认消费组中的消息已被处理。
- `XPENDING`:查询消费组中挂起(未确认)的消息。
- `XCLAIM`:将挂起的消息从一个消费者转移到另一个消费者。
- `XINFO`:获取流(`XINFO STREAM`)、消费组(`XINFO GROUPS`)或消费者(`XINFO CONSUMERS`)的详细信息。
- `XINFO`:获取流`XINFO STREAM`)、消费组(`XINFO GROUPS`)或消费者(`XINFO CONSUMERS`的详细信息。
`Stream` 使用起来相对要麻烦一些,这里就不演示了。
总的来说,`Stream` 已经可以满足一个消息队列的基本要求了。不过,`Stream` 在实际使用中依然会有一些小问题不太好解决比如在 Redis 发生故障恢复后不能保证消息至少被消费一次。
总的来说,`Stream` 已经可以满足一个消息队列的基本要求了。不过,`Stream` 在实际使用中依然会有一些小问题不太好解决比如在 Redis 发生故障恢复后不能保证消息至少被消费一次。
综上,和专业的消息队列相比,使用 Redis 来实现消息队列还是有很多欠缺的地方比如消息丢失和堆积问题不好解决。因此,我们通常建议不要使用 Redis 来做消息队列,你完全可以选择市面上比较成熟的一些消息队列比如 RocketMQ、Kafka。不过如果你就是想要用 Redis 来做消息队列的话,那我建议你优先考虑 `Stream`,这是目前相对最优的 Redis 消息队列实现。
综上,和专业的消息队列相比,使用 Redis 来实现消息队列还是有很多欠缺的地方比如消息丢失和堆积问题不好解决。因此,我们通常建议不要使用 Redis 来做消息队列,你完全可以选择市面上比较成熟的一些消息队列比如 RocketMQ、Kafka。不过如果你就是想要用 Redis 来做消息队列的话,那我建议你优先考虑 `Stream`,这是目前相对最优的 Redis 消息队列实现。
相关阅读:[Redis 消息队列发展历程 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/gCUT5TcCQRAxYkTJfTRjJw)。
@ -274,7 +274,7 @@ RediSearch 支持中文分词、聚合统计、停用词、同义词、拼写检
对于小型项目的简单搜索场景来说,使用 RediSearch 来作为搜索引擎还是没有问题的(搭配 RedisJSON 使用)。
对于比较复杂或者数据规模较大的搜索场景还是不太建议使用 RediSearch 来作为搜索引擎,主要是因为下面这些限制和问题:
对于比较复杂或者数据规模较大的搜索场景还是不太建议使用 RediSearch 来作为搜索引擎,主要是因为下面这些限制和问题:
1. 数据量限制Elasticsearch 可以支持 PB 级别的数据量可以轻松扩展到多个节点利用分片机制提高可用性和性能。RedisSearch 是基于 Redis 实现的,其能存储的数据量受限于 Redis 的内存容量,不太适合存储大规模的数据(内存昂贵,扩展能力较差)。
2. 分布式能力较差Elasticsearch 是为分布式环境设计的,可以轻松扩展到多个节点。虽然 RedisSearch 支持分布式部署,但在实际应用中可能会面临一些挑战,如数据分片、节点间通信、数据一致性等问题。
@ -292,10 +292,10 @@ Elasticsearch 适用于全文搜索、复杂查询、实时数据分析和聚合
基于 Redis 实现延时任务的功能无非就下面两种方案:
1. Redis 过期事件监听
2. Redisson 内置的延时队列
1. Redis 过期事件监听
2. Redisson 内置的延时队列
Redis 过期事件监听存在时效性较差、丢消息、多服务实例下消息重复消费等问题,不被推荐使用。
Redis 过期事件监听存在时效性较差、丢消息、多服务实例下消息重复消费等问题,不被推荐使用。
Redisson 内置的延时队列具备下面这些优势:
@ -328,7 +328,7 @@ String 的常见应用场景如下:
- 常规数据(比如 Session、Token、序列化后的对象、图片的路径的缓存
- 计数比如用户单位时间的请求数(简单限流可以用到)、页面单位时间的访问数;
- 分布式锁(利用 `SETNX key value` 命令可以实现一个最简易的分布式锁)
- 分布式锁(利用 `SETNX key value` 命令可以实现一个最简易的分布式锁)
- ……
关于 String 的详细介绍请看这篇文章:[Redis 5 种基本数据类型详解](https://javaguide.cn/database/redis/redis-data-structures-01.html)。
@ -353,7 +353,7 @@ Redis 是基于 C 语言编写的,但 Redis 的 String 类型的底层实现
SDS 最早是 Redis 作者为日常 C 语言开发而设计的 C 字符串,后来被应用到了 Redis 上,并经过了大量的修改完善以适合高性能操作。
Redis7.0 的 SDS 的部分源码如下(<https://github.com/redis/redis/blob/7.0/src/sds.h>:
Redis7.0 的 SDS 的部分源码如下(<https://github.com/redis/redis/blob/7.0/src/sds.h>
```c
/* Note: sdshdr5 is never used, we just access the flags byte directly.
@ -388,7 +388,7 @@ struct __attribute__ ((__packed__)) sdshdr64 {
};
```
通过源码可以看出SDS 共有五种实现方式 SDS_TYPE_5并未用到、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
通过源码可以看出SDS 共有五种实现方式SDS_TYPE_5并未用到、SDS_TYPE_8、SDS_TYPE_16、SDS_TYPE_32、SDS_TYPE_64其中只有后四种实际用到。Redis 会根据初始化的长度决定使用哪种类型,从而减少内存的使用。
| 类型 | 字节 | 位 |
| -------- | ---- | --- |
@ -400,10 +400,10 @@ struct __attribute__ ((__packed__)) sdshdr64 {
对于后四种实现都包含了下面这 4 个属性:
- `len`:字符串的长度也就是已经使用的字节数
- `alloc`总共可用的字符空间大小alloc-len 就是 SDS 剩余的空间大小
- `buf[]`:实际存储字符串的数组
- `flags`:低三位保存类型标志
- `len`:字符串的长度也就是已经使用的字节数
- `alloc`总共可用的字符空间大小alloc-len 就是 SDS 剩余的空间大小
- `buf[]`:实际存储字符串的数组
- `flags`:低三位保存类型标志
SDS 相比于 C 语言中的字符串有如下提升:
@ -447,7 +447,7 @@ struct sdshdr {
Redis 中有一个叫做 `Sorted Set`(有序集合)的数据类型经常被用在各种排行榜的场景,比如直播间送礼物的排行榜、朋友圈的微信步数排行榜、王者荣耀中的段位排行榜、话题热度排行榜等等。
相关的一些 Redis 命令: `ZRANGE` (从小到大排序)、 `ZREVRANGE` (从大到小排序)、`ZREVRANK` (指定元素排名)
相关的一些 Redis 命令`ZRANGE`(从小到大排序)、`ZREVRANGE`(从大到小排序)、`ZREVRANK`(指定元素排名)
![](https://oss.javaguide.cn/github/javaguide/database/redis/2021060714195385.png)
@ -472,7 +472,7 @@ Redis 中 `Set` 是一种无序集合,集合中的元素没有先后顺序但
`Set` 的常见应用场景如下:
- 存放的数据不能重复的场景:网站 UV 统计(数据量巨大的场景还是 `HyperLogLog` 更适合一些)、文章点赞、动态点赞等等。
- 需要获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集) 等等。
- 需要获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)、音乐推荐(差集)、订阅号推荐(差集+交集)等等。
- 需要随机获取数据源中的元素的场景:抽奖系统、随机点名等等。
### 使用 Set 实现抽奖系统怎么做?
@ -481,11 +481,11 @@ Redis 中 `Set` 是一种无序集合,集合中的元素没有先后顺序但
- `SADD key member1 member2 ...`:向指定集合添加一个或多个元素。
- `SPOP key count`:随机移除并获取指定集合中一个或多个元素,适合不允许重复中奖的场景。
- `SRANDMEMBER key count` : 随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。
- `SRANDMEMBER key count`随机获取指定集合中指定数量的元素,适合允许重复中奖的场景。
### 使用 Bitmap 统计活跃用户怎么做?
Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte所以 Bitmap 本身会极大的节省储存空间。
Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap只需要一个 bit 位来表示某个元素对应的值或者状态key 就是对应元素本身。我们知道 8 个 bit 可以组成一个 byte所以 Bitmap 本身会极大的节省储存空间。
你可以将 Bitmap 看作是一个存储二进制数字0 和 1的数组数组中每个元素的下标叫做 offset偏移量
@ -504,7 +504,7 @@ Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap, 只需
(integer) 0
```
统计 20210308~20210309 总活跃用户数:
统计 20210308~20210309 总活跃用户数
```bash
> BITOP and desk1 20210308 20210309
@ -513,7 +513,7 @@ Bitmap 存储的是连续的二进制数字0 和 1通过 Bitmap, 只需
(integer) 1
```
统计 20210308~20210309 在线活跃用户数:
统计 20210308~20210309 在线活跃用户数
```bash
> BITOP or desk2 20210308 20210309
@ -620,13 +620,13 @@ io-threads 4 #设置1的话只会开启主线程官网建议4核的机器建
- io-threads 的个数一旦设置,不能通过 config 动态设置。
- 当设置 ssl 后io-threads 将不工作。
开启多线程后,默认只会使用多线程进行 IO 写入 writes即发送数据给客户端如果需要开启多线程 IO 读取 reads同样需要修改 redis 配置文件 `redis.conf` :
开启多线程后,默认只会使用多线程进行 IO 写入 writes即发送数据给客户端如果需要开启多线程 IO 读取 reads同样需要修改 redis 配置文件 `redis.conf`
```bash
io-threads-do-reads yes
```
但是官网描述开启多线程读并不能有太大提升,因此一般情况下并不建议开启
但是官网描述开启多线程读并不能有太大提升,因此一般情况下并不建议开启
相关阅读:
@ -695,7 +695,7 @@ OK
### Redis 是如何判断数据是否过期的呢?
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
![Redis 过期字典](https://oss.javaguide.cn/github/javaguide/database/redis/redis-expired-dictionary.png)
@ -724,7 +724,7 @@ typedef struct redisDb {
3. **延迟队列**:把设置过期时间的 key 放到一个延迟队列里,到期之后就删除 key。这种方式可以保证每个过期 key 都能被删除,但维护延迟队列太麻烦,队列本身也要占用资源。
4. **定时删除**:每个设置了过期时间的 key 都会在设置的时间到达时立即被删除。这种方法可以确保内存中不会有过期的键,但是它对 CPU 的压力最大,因为它需要为每个键都设置一个定时器。
**Redis 采用的那种删除策略呢?**
**Redis 采用的那种删除策略呢?**
Redis 采用的是 **定期删除+惰性/懒汉式删除** 结合的策略,这也是大部分缓存框架的选择。定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,结合起来使用既能兼顾 CPU 友好,又能兼顾内存友好。
@ -758,7 +758,7 @@ Redis 7.2 版本的执行时间阈值是 **25ms**,过期 key 比例设定值
在 Redis 中,定期删除的频率是由 **hz** 参数控制的。hz 默认为 10代表每秒执行 10 次,也就是每秒钟进行 10 次尝试来查找并删除过期的 key。
hz 的取值范围为 1~500。增大 hz 参数的值会提升定期删除的频率。如果你想要更频繁地执行定期删除任务,可以适当增加 hz 的值,但这会加 CPU 的使用率。根据 Redis 官方建议hz 的值不建议超过 100对于大部分用户使用默认的 10 就足够了。
hz 的取值范围为 1~500。增大 hz 参数的值会提升定期删除的频率。如果你想要更频繁地执行定期删除任务,可以适当增加 hz 的值,但这会加 CPU 的使用率。根据 Redis 官方建议hz 的值不建议超过 100对于大部分用户使用默认的 10 就足够了。
下面是 hz 参数的官方注释我翻译了其中的重要信息Redis 7.2 版本)。
@ -786,19 +786,19 @@ dynamic-hz yes
因为不太好办到,或者说这种删除方式的成本太高了。假如我们使用延迟队列作为删除策略,这样存在下面这些问题:
1. 队列本身的开销可能很大key 多的情况下,一个延迟队列可能无法容纳。
2. 维护延迟队列太麻烦:修改 key 的过期时间就需要调整期在延迟队列中的位置,并且还需要引入并发控制。
2. 维护延迟队列太麻烦:修改 key 的过期时间就需要调整期在延迟队列中的位置,并且还需要引入并发控制。
### 大量 key 集中过期怎么办?
当 Redis 中存在大量 key 在同一时间点集中过期时,可能会导致以下问题:
- **请求延迟增加** Redis 在处理过期 key 时需要消耗 CPU 资源,如果过期 key 数量庞大,会导致 Redis 实例的 CPU 占用率升高,进而影响其他请求的处理速度,造成延迟增加。
- **内存占用过高** 过期的 key 虽然已经失效,但在 Redis 真正删除它们之前,仍然会占用内存空间。如果过期 key 没有及时清理,可能会导致内存占用过高,甚至引发内存溢出。
- **请求延迟增加**Redis 在处理过期 key 时需要消耗 CPU 资源,如果过期 key 数量庞大,会导致 Redis 实例的 CPU 占用率升高,进而影响其他请求的处理速度,造成延迟增加。
- **内存占用过高**过期的 key 虽然已经失效,但在 Redis 真正删除它们之前,仍然会占用内存空间。如果过期 key 没有及时清理,可能会导致内存占用过高,甚至引发内存溢出。
为了避免这些问题,可以采取以下方案:
1. **尽量避免 key 集中过期**: 在设置键的过期时间时尽量随机一点。
2. **开启 lazy free 机制**: 修改 `redis.conf` 配置文件,将 `lazyfree-lazy-expire` 参数设置为 `yes`,即可开启 lazy free 机制。开启 lazy free 机制后Redis 会在后台异步删除过期的 key不会阻塞主线程的运行从而降低对 Redis 性能的影响。
1. **尽量避免 key 集中过期**在设置键的过期时间时尽量随机一点。
2. **开启 lazy free 机制**修改 `redis.conf` 配置文件,将 `lazyfree-lazy-expire` 参数设置为 `yes`,即可开启 lazy free 机制。开启 lazy free 机制后Redis 会在后台异步删除过期的 key不会阻塞主线程的运行从而降低对 Redis 性能的影响。
### Redis 内存淘汰策略了解么?

View File

@ -28,7 +28,7 @@ Redis 事务实际开发中使用的非常少,功能比较鸡肋,不要将
### 如何使用 Redis 事务?
Redis 可以通过 **`MULTI``EXEC``DISCARD``WATCH`** 等命令来实现事务(Transaction)功能。
Redis 可以通过 **`MULTI``EXEC``DISCARD``WATCH`** 等命令来实现事务Transaction功能。
```bash
> MULTI
@ -47,8 +47,8 @@ QUEUED
这个过程是这样的:
1. 开始事务(`MULTI`
2. 命令入队(批量操作 Redis 的命令先进先出FIFO的顺序执行)
3. 执行事务(`EXEC`)
2. 命令入队(批量操作 Redis 的命令先进先出FIFO的顺序执行
3. 执行事务`EXEC`
你也可以通过 [`DISCARD`](https://redis.io/commands/discard) 命令取消一个事务,它会清空事务队列中保存的所有命令。
@ -138,10 +138,10 @@ Redis 官网相关介绍 [https://redis.io/topics/transactions](https://redis.io
Redis 的事务和我们平时理解的关系型数据库的事务不同。我们知道事务具有四大特性:**1. 原子性****2. 隔离性****3. 持久性****4. 一致性**。
1. **原子性Atomicity** 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **隔离性Isolation** 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
3. **持久性Durability** 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响
4. **一致性Consistency** 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
1. **原子性Atomicity**事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **隔离性Isolation**并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
3. **持久性Durability**一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响
4. **一致性Consistency**:执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的。
Redis 事务在运行错误的情况下除了执行过程中出现错误的命令外其他命令都能正常执行。并且Redis 事务是不支持回滚roll back操作的。因此Redis 事务其实是不满足原子性的。
@ -149,23 +149,23 @@ Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis
![Redis 为什么不支持回滚](https://oss.javaguide.cn/github/javaguide/database/redis/redis-rollback.png)
**相关 issue** :
**相关 issue**
- [issue#452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452)。
- [Issue#491:关于 Redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
- [Issue#491:关于 Redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
### Redis 事务支持持久性吗?
Redis 不同于 Memcached 的很重要一点就是Redis 支持持久化,而且支持 3 种持久化方式:
Redis 不同于 Memcached 的很重要一点就是Redis 支持持久化,而且支持 3 种持久化方式
- 快照snapshottingRDB
- 只追加文件append-only file, AOF
- RDB 和 AOF 的混合持久化(Redis 4.0 新增)
- 快照snapshottingRDB
- 只追加文件append-only fileAOF
- RDB 和 AOF 的混合持久化Redis 4.0 新增)。
与 RDB 持久化相比AOF 持久化的实时性更好。在 Redis 的配置文件中存在三种不同的 AOF 持久化方式(`fsync` 策略),它们分别是:
```bash
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件,fsync完成后线程返回,这样会严重降低Redis的速度
appendfsync always #每次有数据修改发生时都会调用fsync函数同步AOF文件fsync完成后线程返回这样会严重降低Redis的速度
appendfsync everysec #每秒钟调用fsync函数同步一次AOF文件
appendfsync no #让操作系统决定何时进行同步一般为30秒一次
```
@ -190,19 +190,19 @@ Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常
除了下面介绍的内容之外,再推荐两篇不错的文章:
- [你的 Redis 真的变慢了吗?性能优化如何做 - 阿里开发者](https://mp.weixin.qq.com/s/nNEuYw0NlYGhuKKKKoWfcQ)
- [Redis 常见阻塞原因总结 - JavaGuide](https://javaguide.cn/database/redis/redis-common-blocking-problems-summary.html)
- [你的 Redis 真的变慢了吗?性能优化如何做 - 阿里开发者](https://mp.weixin.qq.com/s/nNEuYw0NlYGhuKKKKoWfcQ)
- [Redis 常见阻塞原因总结 - JavaGuide](https://javaguide.cn/database/redis/redis-common-blocking-problems-summary.html)
### 使用批量操作减少网络传输
一个 Redis 命令的执行可以简化为以下 4 步:
1. 发送命令
2. 命令排队
3. 命令执行
4. 返回结果
1. 发送命令
2. 命令排队
3. 命令执行
4. 返回结果
其中,第 1 步和第 4 步耗费时间之和称为 **Round Trip Time (RTT,往返时间)** ,也就是数据在网络上传输的时间。
其中,第 1 步和第 4 步耗费时间之和称为 **Round Trip TimeRTT往返时间**,也就是数据在网络上传输的时间。
使用批量操作可以减少网络传输次数,进而有效减小网络开销,大幅减少 RTT。
@ -212,12 +212,12 @@ Redis 从 2.6 版本开始支持执行 Lua 脚本,它的功能和事务非常
Redis 中有一些原生支持批量操作的命令,比如:
- `MGET`(获取一个或多个指定 key 的值)、`MSET`(设置一个或多个指定 key 的值)
- `HMGET`(获取指定哈希表中一个或者多个指定字段的值)、`HMSET`(同时将一个或多个 field-value 对设置到指定哈希表中)
- `MGET`(获取一个或多个指定 key 的值)、`MSET`(设置一个或多个指定 key 的值)
- `HMGET`(获取指定哈希表中一个或者多个指定字段的值)、`HMSET`(同时将一个或多个 field-value 对设置到指定哈希表中)
- `SADD`(向指定集合添加一个或多个元素)
- ……
不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 `MGET` 无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上,`MGET`可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
不过,在 Redis 官方提供的分片集群解决方案 Redis Cluster 下,使用这些原生批量操作命令可能会存在一些小问题需要解决。就比如说 `MGET` 无法保证所有的 key 都在同一个 **hash slot(哈希槽)** 上,`MGET`可能还是需要多次网络传输,原子操作也无法保证了。不过,相较于非批量操作,还是可以节省不少网络传输次数。
整个步骤的简化版如下(通常由 Redis 客户端实现,无需我们自己再手动实现):
@ -227,15 +227,15 @@ Redis 中有一些原生支持批量操作的命令,比如:
如果想要解决这个多次网络传输的问题,比较常用的办法是自己维护 key 与 slot 的关系。不过这样不太灵活,虽然带来了性能提升,但同样让系统复杂性提升。
> Redis Cluster 并没有使用一致性哈希,采用的是 **哈希槽分区** ,每一个键值对都属于一个 **hash slot**(哈希槽) 。当客户端发送命令请求的时候,需要先根据 key 通过上面的计算公式找到的对应的哈希槽,然后再查询哈希槽和节点的映射关系,即可找到目标 Redis 节点。
> Redis Cluster 并没有使用一致性哈希,采用的是 **哈希槽分区**,每一个键值对都属于一个 **hash slot(哈希槽)**。当客户端发送命令请求的时候,需要先根据 key 通过上面的计算公式找到的对应的哈希槽,然后再查询哈希槽和节点的映射关系,即可找到目标 Redis 节点。
>
> 我在 [Redis 集群详解(付费)](https://javaguide.cn/database/redis/redis-cluster.html) 这篇文章中详细介绍了 Redis Cluster 这部分的内容,感兴趣地可以看看。
#### pipeline
对于不支持批量操作的命令,我们可以利用 **pipeline流水线)** 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 **元素个数**(例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。
对于不支持批量操作的命令,我们可以利用 **pipeline流水线** 将一批 Redis 命令封装成一组,这些 Redis 命令会被一次性提交到 Redis 服务器,只需要一次网络传输。不过,需要注意控制一次批量操作的 **元素个数**(例如 500 以内,实际也和元素字节数有关),避免网络传输的数据量过大。
`MGET``MSET`等原生批量操作命令一样pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
`MGET``MSET` 等原生批量操作命令一样pipeline 同样在 Redis Cluster 上使用会存在一些小问题。原因类似,无法保证所有的 key 都在同一个 **hash slot(哈希槽)** 上。如果想要使用的话,客户端需要自己维护 key 与 slot 的关系。
原生批量操作命令和 pipeline 的是有区别的,使用的时候需要注意:
@ -263,7 +263,7 @@ Lua 脚本同样支持批量操作多条命令。一段 Lua 脚本可以视作
不过, Lua 脚本依然存在下面这些缺陷:
- 如果 Lua 脚本运行时出错并中途结束,之后的操作不会进行,但是之前已经发生的写操作不会撤销,所以即使使用了 Lua 脚本,也不能实现类似数据库回滚的原子性。
- Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 **hash slot**(哈希槽)上。
- Redis Cluster 下 Lua 脚本的原子操作也无法保证了,原因同样是无法保证所有的 key 都在同一个 **hash slot(哈希槽)** 上。
### 大量 key 集中过期问题
@ -339,7 +339,7 @@ Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28
0 zsets with 0 members (00.00% of keys, avg size 0.00
```
从这个命令的运行结果,我们可以看出:这个命令会扫描(Scan) Redis 中的所有 key ,会对 Redis 的性能有一点影响。并且,这种方式只能找出每种数据结构 top 1 bigkey占用内存最大的 String 数据类型,包含元素最多的复合数据类型)。然而,一个 key 的元素多并不代表占用内存也多,需要我们根据具体的业务情况来进一步判断。
从这个命令的运行结果,我们可以看出:这个命令会扫描ScanRedis 中的所有 key,会对 Redis 的性能有一点影响。并且,这种方式只能找出每种数据结构 top 1 bigkey占用内存最大的 String 数据类型,包含元素最多的复合数据类型)。然而,一个 key 的元素多并不代表占用内存也多,需要我们根据具体的业务情况来进一步判断。
在线上执行该命令时,为了降低对 Redis 的影响,需要指定 `-i` 参数控制扫描的频率。`redis-cli -p 6379 --bigkeys -i 3` 表示扫描过程中每次扫描后休息的时间间隔为 3 秒。
@ -363,8 +363,8 @@ Biggest string found '"ballcat:oauth:refresh_auth:f6cdb384-9a9d-4f2f-af01-dc3f28
网上有现成的代码/工具可以直接拿来使用:
- [redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools)Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
- [rdb_bigkeys](https://github.com/weiyanwei412/rdb_bigkeys) : Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
- [redis-rdb-tools](https://github.com/sripathikrishnan/redis-rdb-tools)Python 语言写的用来分析 Redis 的 RDB 快照文件用的工具
- [rdb_bigkeys](https://github.com/weiyanwei412/rdb_bigkeys)Go 语言写的用来分析 Redis 的 RDB 快照文件用的工具,性能更好。
**4、借助公有云的 Redis 分析服务。**
@ -497,10 +497,10 @@ hotkey 的常见处理以及优化办法如下(这些方法可以配合起来
我们知道一个 Redis 命令的执行可以简化为以下 4 步:
1. 发送命令
2. 命令排队
3. 命令执行
4. 返回结果
1. 发送命令
2. 命令排队
3. 命令执行
4. 返回结果
Redis 慢查询统计的是命令执行这一步骤的耗时,慢查询命令也就是那些命令执行时间较长的命令。
@ -527,13 +527,13 @@ Redis 中的大部分命令都是 O(1)时间复杂度,但也有少部分 O(n)
`redis.conf` 文件中,我们可以使用 `slowlog-log-slower-than` 参数设置耗时命令的阈值,并使用 `slowlog-max-len` 参数设置耗时命令的最大记录条数。
当 Redis 服务器检测到执行时间超过 `slowlog-log-slower-than`阈值的命令时,就会将该命令记录在慢查询日志(slow log) 中,这点和 MySQL 记录慢查询语句类似。当慢查询日志超过设定的最大记录条数之后Redis 会把最早的执行命令依次舍弃。
当 Redis 服务器检测到执行时间超过 `slowlog-log-slower-than` 阈值的命令时,就会将该命令记录在慢查询日志slow log中,这点和 MySQL 记录慢查询语句类似。当慢查询日志超过设定的最大记录条数之后Redis 会把最早的执行命令依次舍弃。
⚠️注意:由于慢查询日志会占用一定内存空间,如果设置最大记录条数过大,可能会导致内存占用过高的问题。
`slowlog-log-slower-than``slowlog-max-len`的默认配置如下(可以自行修改)
`slowlog-log-slower-than``slowlog-max-len` 的默认配置如下(可以自行修改)
```nginx
```properties
# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
@ -555,7 +555,7 @@ CONFIG SET slowlog-max-len 128
获取慢查询日志的内容很简单,直接使用 `SLOWLOG GET` 命令即可。
```java
```bash
127.0.0.1:6379> SLOWLOG GET #慢日志查询
1) 1) (integer) 5
2) (integer) 1684326682
@ -593,7 +593,7 @@ OK
**相关问题**
1. 什么是内存碎片?为什么会有 Redis 内存碎片?
1. 什么是内存碎片?为什么会有 Redis 内存碎片?
2. 如何清理 Redis 内存碎片?
**参考答案**[Redis 内存碎片详解](https://javaguide.cn/database/redis/redis-memory-fragmentation.html)。
@ -616,7 +616,7 @@ OK
**1缓存无效 key**
如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086` 。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key会导致 Redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
如果缓存和数据库都查不到某个 key 的数据就写一个到 Redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如果黑客恶意攻击,每次构建不同的请求 key会导致 Redis 中缓存大量无效的 key。很明显这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
另外,这里多说一嘴,一般情况下我们是这样设计 key 的:`表名:列名:主键名:主键值`
@ -655,7 +655,7 @@ Bloom Filter 会使用一个较大的 bit 数组来保存所有的数据,数
具体是这样做的:把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。
加入布隆过滤器之后的缓存处理流程图如下
加入布隆过滤器之后的缓存处理流程图如下
![加入布隆过滤器之后的缓存处理流程图](https://oss.javaguide.cn/github/javaguide/database/redis/redis-cache-penetration-bloom-filter.png)
@ -707,15 +707,15 @@ Bloom Filter 会使用一个较大的 bit 数组来保存所有的数据,数
#### 有哪些解决办法?
**针对 Redis 服务不可用的情况**
**针对 Redis 服务不可用的情况**
1. **Redis 集群**:采用 Redis 集群避免单机出现问题整个缓存服务都没办法使用。Redis Cluster 和 Redis Sentinel 是两种最常用的 Redis 集群实现方案,详细介绍可以参考:[Redis 集群详解(付费)](https://javaguide.cn/database/redis/redis-cluster.html)。
2. **多级缓存**:设置多级缓存,例如本地缓存+Redis 缓存的二级缓存组合,当 Redis 缓存出现问题时,还可以从本地缓存中获取到部分数据。
**针对大量缓存同时失效的情况**
**针对大量缓存同时失效的情况**
1. **设置随机失效时间**(可选):为缓存设置随机的失效时间,例如在固定过期时间的基础上加上一个随机值,这样可以避免大量缓存同时到期,从而减少缓存雪崩的风险。
2. **提前预热**(推荐):针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
2. **提前预热**(推荐):针对热点数据提前预热,将其存入缓存中并设置合理的过期时间比如秒杀场景下的数据在秒杀结束之前不过期。
3. **持久缓存策略**(看情况):虽然一般不推荐设置缓存永不过期,但对于某些关键性和变化不频繁的数据,可以考虑这种策略。
#### 缓存预热如何实现?
@ -739,8 +739,8 @@ Cache Aside Pattern 中遇到写请求是这样的:更新数据库,然后直
如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说有两个解决方案:
1. **缓存失效时间变短(不推荐,治标不治本)**:我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
2. **增加缓存更新重试机制(常用)**:如果缓存服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。不过,这里更适合引入消息队列实现异步重试,将删除缓存重试的消息投递到消息队列,然后由专门的消费者来重试,直到成功。虽然说多引入了一个消息队列,但其整体带来的收益还是要更高一些。
1. **缓存失效时间变短**(不推荐,治标不治本):我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
2. **增加缓存更新重试机制**(常用):如果缓存服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。不过,这里更适合引入消息队列实现异步重试,将删除缓存重试的消息投递到消息队列,然后由专门的消费者来重试,直到成功。虽然说多引入了一个消息队列,但其整体带来的收益还是要更高一些。
相关文章推荐:[缓存和数据库一致性问题,看这篇就够了 - 水滴与银弹](https://mp.weixin.qq.com/s?__biz=MzIyOTYxNDI5OA==&mid=2247487312&idx=1&sn=fa19566f5729d6598155b5c676eee62d&chksm=e8beb8e5dfc931f3e35655da9da0b61c79f2843101c130cf38996446975014f958a6481aacf1&scene=178&cur_album_id=1699766580538032128#rd)。
@ -753,10 +753,10 @@ Cache Aside Pattern 中遇到写请求是这样的:更新数据库,然后直
**Redis Sentinel**
1. 什么是 Sentinel 有什么用?
2. Sentinel 如何检测节点是否下线?主观下线与客观下线的区别?
2. Sentinel 如何检测节点是否下线?主观下线与客观下线的区别
3. Sentinel 是如何实现故障转移的?
4. 为什么建议部署多个 sentinel 节点(哨兵集群)?
5. Sentinel 如何选择出新的 master选举机制?
5. Sentinel 如何选择出新的 master选举机制
6. 如何从 Sentinel 集群中选择出 Leader
7. Sentinel 可以防止脑裂吗?
@ -764,7 +764,7 @@ Cache Aside Pattern 中遇到写请求是这样的:更新数据库,然后直
1. 为什么需要 Redis Cluster解决了什么问题有什么优势
2. Redis Cluster 是如何分片的?
3. 为什么 Redis Cluster 的哈希槽是 16384 个?
3. 为什么 Redis Cluster 的哈希槽是 16384 个
4. 如何确定给定 key 的应该分布到哪个哈希槽中?
5. Redis Cluster 支持重新分配哈希槽吗?
6. Redis Cluster 扩容缩容期间可以提供服务吗?
@ -790,10 +790,10 @@ Cache Aside Pattern 中遇到写请求是这样的:更新数据库,然后直
- 《Redis 开发与运维》
- 《Redis 设计与实现》
- Redis Transactions : <https://redis.io/docs/manual/transactions/>
- Redis Transactions<https://redis.io/docs/manual/transactions/>
- What is Redis Pipeline<https://buildatscale.tech/what-is-redis-pipeline/>
- 一文详解 Redis 中 BigKey、HotKey 的发现与处理:<https://mp.weixin.qq.com/s/FPYE1B839_8Yk1-YSiW-1Q>
- Bigkey 问题的解决思路与方式探索:<https://mp.weixin.qq.com/s/Sej7D9TpdAobcCmdYdMIyA>
- Bigkey 问题的解决思路与方式探索<https://mp.weixin.qq.com/s/Sej7D9TpdAobcCmdYdMIyA>
- Redis 延迟问题全面排障指南:<https://mp.weixin.qq.com/s/mIc6a9mfEGdaNDD3MmfFsg>
<!-- @include: @article-footer.snippet.md -->

View File

@ -6,6 +6,7 @@ tag:
---
> 本文由 JavaGuide 翻译自 [https://www.baeldung.com/jvm-parameters](https://www.baeldung.com/jvm-parameters),并对文章进行了大量的完善补充。
> 文档参数 [https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html)
>
> JDK 版本1.8
@ -71,10 +72,10 @@ GC 调优策略中很重要的一条经验总结是这样说的:
另外,你还可以通过 **`-XX:NewRatio=<int>`** 来设置老年代与新生代内存的比值。
比如下面的参数就是设置老年代与新生代内存的比值为 1。也就是说老年代和新生代所占比值为 11新生代占整个堆栈的 1/2
比如下面的参数就是设置新生代与老年代内存的比值为 2默认值。也就是说 young/old 所占比值为 2新生代占整个堆栈的 2/3
```plain
-XX:NewRatio=1
-XX:NewRatio=2
```
### 2.3.显式指定永久代/元空间的大小