接上篇笔记:https://blog.csdn.net/weixin_44780078/article/details/130192373
InnoDB 和 MyISAM 都是 MySql 的存储引擎。
| InnoDB | MyISAM | |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 主键 | 一定要有 | 不一定要有 |
| 外键 | 支持 | 不支持 |
| 聚簇索引 | 是(数据文件和索引文件是绑定在一起的) | 不是(相反数据文件和索引文件是分开的) |
| 全文索引 | 不支持(v5.7之后也支持) | 支持 |
| 行数 | 不存储 | 存储 |
| 行锁 | 支持 | 不支持 |
InnoDB: InnoDB 在 mysql 3 版本就出现了,但是是在 5.5版本之后才成为 mysql 默认的存储引擎,InnoDB 最大的好处就是支持事务、支持外键和支持行级锁,它也是聚簇索引,但它不支持全文索引(5.7版本之后也支持)。
MyISAM:是 mysql 5.5版本之前默认的存储引擎,不支持事务、不支持外键、不支持行级锁,也不是聚簇索引,因此也导致了在 5.5版本之后 mysql 把存储引擎改为了 InnoDB,它支持全文索引。
对于主键:
对于整型的自增方式:
explain 中 type:all 表示全表通查。
explain select * from fangyi_user.user

explain 中 type:index 表示全表扫描索引文件(但是只遍历索引树)。
explain select user_id from fangyi_user.user // user_id是主键

explain 中 type:range 表示检索给定范围的行,使用一个索引来选择行。(不需要优化了)
explain select * from fangyi_user.user WHERE user_id > 1

explain 中 type:const 表示最多有一个匹配行,读取1次,速度非常快。(不需要优化了)
explain select * from fangyi_user.user WHERE user_id = 5661

explain type 级别:system > const > eq_ref > ref > range > index > all.
SQL 优化目标:至少要达到 range 级别,要求是 ref 级别,如果是 const 最好。
简单的说聚簇索引就是索引和数据文件是放在一个位置的,非聚簇索引是把索引和数据文件分开存储。
我们都知道 mysql 的底层采用的是 B+ 树作为索引结构,对于 mysql 的存储引擎 又分为 MyISAM 和 InnoDB,假如对同一张表添加索引:



因此不难得出 InnoDB 是采用聚簇索引,MyISAM 采用的是非聚簇索引。并且聚簇索引的效率更高,因为不需要再通过内存地址去进行二次查找。
对于普通字段建立索引(非主键索引),mysql 也是采用 B+ 树进行存储,但是 B+ 树的叶子结点存储的是该表的主键,查出主键过后再通过主键索引进行二次查看,然后得出最终的数据(也就是二级索引)。
一般对于一张数据库表,不推荐给单个字段建立太多的索引,而是建立联合索引(给多个字段联合起来建立一个索引)

比如对于这个表,对 dept_name 和 dept_address 、num 三列建立联合索引:
create index index_name_address_num on dept(dept_name, dept_address, num);
联合索引遵循一种最左前缀原则,顾名思义就是最左优先,以最左边的列为起点任何连续的索引都能匹配上。如果没有第一列作为查询条件的话,直接访问第二列,那第二列肯定是无序的,直接访问后面的列索引也不会生效。当创建 (a,b,c) 联合索引时,想要索引生效的话,只能使用 a、ab、ac 和 abc 四种组合!
假如创建(a,b,c)联合索引,底层的 B+ 树是先按列 a 进行排序,排完过后再按照列 b,依次类推,直到排序所有列。如果列 a 存在相同的值,则再排序列 b。这样查询起来就很方便,因此必须要遵顼最左前缀原则。
在 mysql 中索引是在存储引擎层实现的,而不是在服务器层实现,所以不太存储引擎具有不同的索引类型和实现,常见的索引分类如下:
在 mysql 中,如果是 InnoDB 存储引擎,主键索引的 B+ 树叶子结点上存储的是整行数据的值,而二级索引(比如单个列建立的索引、多个列建立的联合索引,所有非主键索引都可以称之为二级索引)的 B+ 树叶子结点上,存储的是该行数据的主键,也就是通过二级索引找到主键后,再通过主键去查询具体的值,这就是二级索引。这种进行二次操作的查询也称为回表。
在上面提到的二级索引会进行回表操作,那是因为要通过主键查其他的列数据,假如执行这条 sql:
select id from user where age = 18;
只查询主键 id,那还会进行回表查询吗?答案是不会,因为通过 age 列的普通索引,已经查询出了主键,不必再通过主键去进行回表查询。这种就称为覆盖查询。
答案是会,InnoDB 是 mysql 中的一种存储引擎,它会为每个表创建一个主键索引。如果表没有明确的主键索引,InnoDB 会使用一个隐藏的、自动生成的主键(row_id)来创建索引,这个 row_id 使用的就是 B+ 树,因此,在 InnoDB 中,即使没有明确的主键索引,也会创建 B+ 树索引。
什么是事务?:多条sql语句,要么全部执行成功,要么全部执行失败。
事务的四大特性:
事务的隔离级别就是在并发环境下,事务之间的隔离程度,事务有四种隔离级别。
mysql 默认的隔离级别是可重复读。
事务的四种隔离级别主要解决三种实际问题:脏读、不可重复读、幻读。都是在并发事务下才会发生。
查询语句中不要使用select *;
- 性能原因:当使用 select * 查询表中的所有列时,数据库需要查询和返回表中的每一列,包括不需要的列,这会导致性能的下降。对于 sql 语句而言,使用 select * sql 语句会先去查询表中的列,再根据列去查询数据,而直接查询具体的列可以避免这一操作。
- 数据安全:使用 select * 可能会暴露数据库中的敏感数据,例如密码。
- 冗余数据:使用 select * 可能会查询例如 text 这类不必要的数据,增加网络的开销。
尽量减少子查询,使用关联查询(left join,right join,inner join)替代;
- 性能优化:子查询往往需要执行多次,每次执行都会涉及到查询和计算,增加了数据可的开销。而关联查询可以将多个查询合并为一次查询,减少了数据库的访问次数和计算量,提高了查询的性能。
- 代码简洁性:子查询的语法较复杂,嵌套层次多,可读性查,维护起来比较困难,而关联查询的语法相对简单,易于理解和维护,代码可读性更高。
- 可扩展性:使用关联查询可以方便的添加更多的表和连接条件,灵活性更高。
- 可优化性:关联查询可以索引等优化手段来提高查询性能,而子查询的性能优化相对较难。

对于 mysql 的优化,从上图可以看出成本越低的反而效果越明显,因此作为程序员我们要重点掌握在 SQL 及索引层去优化。关于 SQL 优化方法,包括五点:
在 mysql 5.7版本之前,explain 的使用有差别,此处只从 5.7 版本之后进行讲解。
explain 语句返回列介绍:
explain select * from dept where num > 1

| 列名 | 含义 |
|---|---|
| id | 每个 select 都有一个对应的 id 号,并且是从 1 开始自增的 |
| select_type | 查询语句执行的查询操作类型 |
| table | 表名 |
| partions | 表分区情况 |
| * type | 查询所用的访问类型 |
| possible_keys | 可能用到的索引 |
| key | 实际查询到的索引 |
| key_len | 所用到的索引长度 |
| ref | 使用到索引时,与索引进行等值匹配的列或常量 |
| rows | 预计扫描的行数(索引行树或者表记录行数) |
| filtered | 表示符合查询条件的数据百分比 |
| extra | sql 执行的额外信息 |
对于 id,举例关联查询和子查询。
select_type:表示查询语句执行的查询操作类型。
table :查询所涉及的表名。如果有多个表,将显示多行记录。
partitions :表分区情况。
type :效率从高到低分别为:system > const > eq_ref > ref > fulltext > ref_or_null > range > index > ALL,一般来说保证range级别,最好能达到ref级别。
possible_keys :表示在查询中可能使用到某个索引或多个索引;如果没有选择索引,显示 NULL。
key :表示在查询中实际使用的索引,如果没有使用索引,显示 NULL。
key_len :表示当优化器决定使用某个索引执行查询时,该索引记录的最大长度(主要使用在联合索引)
ref :表示将哪个字段或常量和 key 列所使用的字段进行比较。
rows :全表扫描时表示需要扫描表的行数估计值;索引扫描时表示扫描索引的行数估计值;值越小越好(不是结果集中的行数)。
filtered :表示符合查询条件的数据百分比。可以使用rows * filtered/100计算出与explain前一个表进行连接的行数。
Extra :SQL执行查询的一些额外信息。
1、在索引列上做计算、使用函数、类型转换,会导致索引失效。
// 例:
select * from dept where left(dept_name, 2) = '信息'; // 索引失效
select * from dept where dept_name like '信息%'; // sql改造,不失效
2、索引列如果使用了范围查询,范围查询之后的列索引会失效。
// 索引正常
explain select * from dept where dept_name = '人力资源部' and num = 3 and dept_address = 'xx大厦7楼';
// dept_address 列索引失效
explain select * from dept where dept_name = '人力资源部' and num > 3 and dept_address = 'xx大厦7楼';
// num 和 dept_address 列索引失效
explain select * from dept where dept_name > '人力资源部' and num = 3 and dept_address = 'xx大厦7楼';
3、查询语句中使用了 !=、is null、or 可能不会走索引。
4、对于 type 为 all,possible_keys 存在索引的,可以使用 force index (索引名) 强制使用索引。
// 例:
select * from person force index(index_name_age_sex) where name != '李四' and age = 33 and sex = '男';
explain select * from person where name like '%四';

explain select * from person where name like '李%';

假如还是需要使用左模糊,可以使用覆盖索引提升效率。
explain select name, age, sex(建立了索引的列) from person where name like ‘%四’;
// 开启trace
set optimizer_trace = "enabled=on", end_markers_in_JSON = on;
开启后再次查看 sql 的 trace 跟踪。同时运行两条 sql。


可以看到使用了 select * 后mysql底层会先把 * 转换成具体的列,然后对全部通查,使用索引等进行评分,哪个更优就使用哪个。
group by 后面的列如果没有索引,那么group by 的效率会很低,
Spring 是一个轻量的、开源的 java 框架,用于搭建企业级应用程序,Spring 框架的主要目标是提供一种简化的 java 开发方式,它提供了许多功能模块,比如依赖注入、面向切面编程、数据访问、web 开发等等。Spring 框架还有很多扩展的模块,比如 SpringBoot、SpringCloud等,用以支持更丰富的应用开发。
1、轻量、无侵入:
Spring 是轻量的,基本的版本只有 2MB 左右。无侵入指的是可以在 Spring 中自由选择和组装 Spring 框架的各个功能模块,不会强制要求开发人员使用特定的类或接口。
2、依赖注入(DI):
Spring 框架提供了依赖注入的功能,可以通过配置文件或注解将对象之间的依赖关系交由 Spring 容器来管理。这就简化了对象之间的耦合度,方便开发人员更好的开发和维护。
3、控制反转(IOC):
在传统的程序设计当中,是通过 new 关键字来创建对象,也就是由程序去主动创建并控制这个依赖对象,而在 Spring 中有一个专门用来创建和管理对象的容器,这个容器就是 IoC 容器。对于某一个具体的对象而言,原本它要控制其他的依赖对象,但是现在所有的对象统一被 Spring 控制,所以这叫控制反转。
4、面向切面的编程(AOP):
面向切面编程是一种编程思想,主要在程序运行期间会使用到。光这样子理解可能有点抽象,举个不恰当的例子:洗澡。
对于男生:脱衣服、洗头、洗脸、洗身体、唱歌、擦干身体、穿衣服。
对于女生:脱衣服、洗头、洗脸、洗身体、擦干身体、护肤、整理头发、穿衣服。
我们发现,不管是男生还是女生,最开始的脱衣服和最终的穿衣服都是必不可少的过程,但是这两个步骤在洗澡这个业务里面又不是最核心的,它只是一个关注点(最核心的应该是洗头、洗身体)。如果把洗澡的每个步骤都当成是一个对象的话,站在面向对象的角度,整个洗澡的过程就是一个纵向的过程,但是如果我们站在横向的角度,把脱衣服、穿衣服这两个耦合的步骤提取出来封装成一个可复用的模块,进行统一的管理,因此这种横向的思考方式就是面向切面编程。
面向切面编程最典型的应用就是日志记录、全局异常处理等等。
自我理解总结的 Spring MVC 执行步骤共有10个,具体如下:
等等还有其他,这里只举例部分。
我们一般使用 @Autowired 注解去自动装配 bean。但是也有其他的注解进行声明 bean:
Spring Boot是一个开源的 Java 框架,用于创建独立的、基于 Spring 的、但是比 Spring 更加简化了的应用程序。它简化了 Spring 应用的配置和部署过程,使开发起来更加高效、方便。
Spring Boot提供了一种约定优于配置的方式,可以通过自动配置的方式开发,因此减少了繁琐的配置文件。它也集成了许多常用的第三方框架,如Spring Security等,使得开发者能够更容易地使用这些框架。
Spring Boot还提供了一个内嵌的Web服务器,如Tomcat,使得应用程序可以快速启动和运行。
总之,Spring Boot是一个简化了的Spring框架开发工具,使开发者能够更加快速、方便地创建和部署Java应用程序。
Redis 有 5 种数据结构,分别是 String:字符串、Hash:hash表、list:集合、Set:set集合(无序不重复)、zset集合(有序不重复)。
mysql、oracle 等他们是存储在磁盘中的,所以读取效率不是很高,redis 的 IO 读取效率非常高,是因此它是存储在内存中的,因此消耗的是内存资源。
Redis 对外提供的键值存储,如set、get等,是单线程的,也就是网络 IO 和数据读写都是单线程。但是为啥单线程的 Redis 效率还是如此的高?
相同点:
不同点:
使用 MQ 最核心的场景有三种:
异步
异步主要是减少服务端的响应时间。比如美团平台的借钱入口,后台的处理步骤大概是这样的:首先用户提交信息,后台会根据用户信息调用一些第三方的接口,比如调用蚂蚁金服查询芝麻分、调用法院官网查询此人是不是老赖、调用银行接口查询此人的征信是否良好、调用相关政府部门的接口查询此人是否有房产车产等,然后再进行综合评估得出一个可借款金额。
美团后台的这些步骤,如果设计成同步的,则用户体验就十分不好,因为要依次去调用每个接口,这样势必会浪费大量的时间,如果使用 MQ 设计成异步的话,异步去调用每一个接口,这样用户的响应时间就是后台处理 + 调用最费时的那个接口的时间和,这样就大大降低了这个借钱接口的响应时间,从而提升用户的体验。
tips:MQ 还有一个好处就是,假如在调用其他服务的过程中,其他服务宕机了,等到其他服务恢复过后 MQ 还会继续调用。如果是同步的话一个服务宕机后续步骤就无法进行了。
解耦
还是刚才那个例子,假如响应用户可借款金额后,还要发送短信提醒用户,这时也要调用短信服务,把用户加入到会员表后,后期也需要对用户进行数据分析,采用 MQ 就能有效的对代码进行解耦,假如后期还需要加入其他服务,也比较方便。
削峰(抗极端的并发请求)
比如像淘宝京东那些电商平台,一到双十一或者6-18,用户量会大大增加,全国14亿的人口可想而知后端服务的压力有多大,因此使用 MQ 可以对服务进行削峰,请求会通过 MQ 平缓的到达后端,对后端服务进行缓冲保护。
1、RabbitMQ:RabbitMQ 支持 AMQP 协议,AMQP 就是进程间传递异步消息的网络协议。RabbitMQ 最大的特点就是轻量,它的交换机和队列都可以通过 Java 代码去创建,非常利于使用和部署。RabbitMQ 支持的语言也比较多,比如 Java、python、go等等。
比如当消息堆积的时候,会导致性能下降。
RabbitMQ 的性能相对 Kafka、RocketMQ 来说,性能不及它们,如果对消息队列的性能要求非常高,那么就不要选择 RabbitMQ。因此 RabbitMQ 适用于中小型项目,不适用于大项目。
RabbitMQ 底层是采用 Erlang 开发的,扩展和二次开发成本比较高。
首先了解一下消息从生产者到消费者的步骤:
生产者生产消息 --> 到达 MQ 服务器的交换机 --> 交换机对消息进行路由到具体的队列 --> 队列再把队列中的消息投递至消费者。
要保证消息不丢失:
导致 MQ 中的消息重复有几类原因:
站在生产者的角度:
站在 MQ 服务器的角度:
消息重复的情况不可避免,因此要解决消息重复问题,我们可以进行幂等操作。
消息幂等性处理就是对于多条重复处理的消息,最终都得到同样的结果,举个简单的例子就是,比如使用 MQ 异步去扣减库存,就算扣减库存的操作重复了,最终也只扣减一次,不会重复扣减。
MQ 是可以确保消息的可靠发送和接收的,一条消息的完整处理步骤大概是这样的:
RabbitMQ 是支持死信队列和延时队列的。
死信队列:死信队列是一种特殊的队列,用于存储无法被消费者正确处理的消息,当消息满足预设的前置条件时,就会投递到死信队列中,比如超过了重复投递的阈值,或者消息过期了。对于我们程序员来说,可以对死信队列进行分析和后续处理。
死信队列创建步骤:
延时队列:延时队列是一种特殊的队列,用于延迟处理投递的消息。当消息进入延时队列后,消息将在设定的延迟时间后才会被投递给消费者处理。
使用延时队列的步骤:
每天都在更新中。。。