编程导航面试题挑战话题讨论

面试题挑战

199 参与
分享

快来分享你的内容吧~

点击登录,快来和大家讨论吧~
表情
图片
话题
打卡
综合
交流
文章
问答

JVM结构分享~可以看一下我的这个网站~嘿嘿~

http://115.190.239.43:8888/ ![image.png](https://pic.code-nav.cn/post_picture/1955809212810784770/YY4VSX0ifhsC27HJ.webp)

TT 电商直播 后端一面 面经

# TT 电商直播 后端一面 公众号:Kevin 的技术小屋 原题放送: 自我介绍,选取了实验室项目进行答辩 1. 为什么你会考虑用 Redis 去优化 MySQL 的查询效率? 2. 你有没有解决过 MySQL 的慢查询问题?怎么解决的? 3. 系统的架构设计能不能跟我说一下? 4. 从键入 www.baidu.com 到屏幕显示百度的图标经历了哪些过程? 5. 追问:究竟到了哪一步,找到了 MAC 地址? 6. 追问:你认为 ARP 协议的作用是什么? 7. 追问:IP 包究竟是怎么找到百度的服务器的?你说的 DNS 的迭代法和递归法是不是跑题了? 8. 追问:你提到了 ICMP 协议,请问这个协议是干什么用的? 9. 操作系统的文件系统我有三个文件,请问三个文件是怎么写进磁盘里面的? 10. 既然你提到了 Linux 系统上的 inode,请你讲一讲 Linux 的文件系统的解法? 11. 操作系统文件管理子系统是怎么管理文件的? 12. 什么是硬链接?什么是软连接? 13. 把 Linux 文件从 /usr 移动到 /root,中间的全流程你能跟我讲清楚吗? 14. Linux 怎么创建软连接你还记得吗? 15. MySQL 的关键字:JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN 能给我解释一下吗? 两道题(MEDIUM 偏 EASY) SQL 题:给你两张表 student (stu_id, name) score (class_id, stu_id, score) 查询课程 ID 为 5 并且考试分数高于 90 分的学生 SELECT (DISTINCT) name FROM student JOIN score ON student.stu_id = score.stu_id WHERE class_id = 5 AND score > 90; 算法题:LC3 https://leetcode.cn/problems/longest-substring-without-repeating-characters/

特训营总结(内含知识点汇总框架)

心路历程 ---- ### 误打误撞:就这?😂 训练营刚开始时,当时看到宣传页的大纲都是 MySQL,而且都是熟悉的八股文。自己自以为是的想着准备了这么久的八股文,这也太简单了吧? 后来看到鱼皮哥直播宣传,想着算是督促自己寒假过年前保持八股的手感吧,反正也是满返就加入了。 ### 发现不对:布豪!😨 进来后就被"打脸"了,刚开始几天很轻松,结果一个星期发现不对: * 这个半一致读是个啥?count(*)和 count(字段)区别是啥??MySQL 为什么默认隔离级别是 RR??? * varchar 肯定比 char 好,那为啥不是越长越好?原来深分页还能从设计上去解决! ### 认真学习:老实了🤓 被面试官拷打了... 包括后面设计模式、Redis、计网等等也补充了很多知识漏洞,而且很多熟悉的问题看拓展也能学到东西,甚至能和面试官吹吹。 知识总结 ---- 寒假前主要以训练营为主,学习 MySQL redis Java 设计模式 OS 计网八股,然后以此拓展去学习其他知识点,收获颇多🥰: 大纲:(训练营的问题 + 想出来的拓展与学习) * MySQL:因为 MySQL 在此次训练营占了很大篇幅,也是面试重中之重,因此单拎出来: ![](https://pic.code-nav.cn/post_picture/1627854772813975553/cOQuWbtYlkExMcZQ.webp) * 其他:包含 Java 语言、Redis、OS、操作系统、设计模式 <img src="https://pic.code-nav.cn/post_picture/1627854772813975553/eOLaT3EXO9OkIMuW.webp" alt="" width="100%" /> 反思 -- 1. 知识点还可以学习的更好 Redis 、计网、MySQL 的部分知识不太熟(已标记),需要过完年巩固巩固 2. 面试表达有待提高。 语音输入答案能力明显越来越差(到后面说着说着直接卡壳了...),越不面试越没感觉。年后跟着几个面经练一下 3. 后面有几天学习状态不太好,需要补回来

Redis 集群的实现原理是什么?

​ Redis 集群的实现原理是什么? ----------------- ### 概述 ​ redis集群是通过多个redis实例组成的。每个实例存储**部分数据(每个实例中的数据是不重复的).** ​ ### 详解: ​ * **实现机制**: * 哈希槽机制来分配数据,将整个键空间划分成16384个槽。 * **存储:** 每个实例负责一定范围的哈希槽,数据的key经过哈希函数计算后,对16384取余,即可定位到对应的节点。 * **查询:** * 客户端在发送请求的时候,会通过集群的任意节点进行连接 * 如果该节点存储了对应的数据则直接返回 * 反之会根据该请求的键值计算哈希槽,并路由到正确的节点。 ​ ### redis集群中节点之间是怎么实现信息同步的? ​ 1. redis集群内每个节点都会保存集群的完整拓扑信息,包括每个节点的ID、IP地址、端口、负责的哈希槽范围等。 2. 节点之间使用Gossip协议进行状态交换,以保持集群的一致性和故障检测。 3. 每个节点会周期性的发送PING和PONG消息,交换集群信息,使得集群信息得以同步。 ​ ### Gossip协议是什么? ​ Gossip协议主要特点: 1. **分布式信息传播**: 每个节点定期向其他节点发送其状态信息,确保所有节点对集群的状态有一致的视图。 2. **低延迟和高效率**: Gossip协议设计为轻量级的通信方式,能够快速传播信息,减少单点故障带来的风险。 3. **去中心化**: 没有中心节点,所有节点平等的参与信息传播,提高了系统的鲁棒性。 工作原理: 1. **报告状态**: 每个节点在特定的事件间隔内,向随机选择的其他节点发送其自身的状态信息,包括节点的主从关系、槽位分布等。 2. **信息更新**: 接收到状态信息的节点会根据所接受的数据更新自己的状态,并将更新后的状态继续传播给其他节点。 3. **节点检测**: 通过周期性交换状态信息,节点可以检测到其他节点的存活状态。如果某个节点未能在预定时间响应,则该节点会被标记为故障节点。 4. **容错处理**: 在检测到节点故障后,集群中的其他节点可以采取措施,例如:重新分配槽位,以保持系统的高可用性。 Gossip协议的优点: 1. **快速收敛**: Goosip协议能够快速传播信息,确保集群状态的迅速更新。 2. **降低网络负担**:由于信息是以随机节点间的对话方式传播,避免了集中式的状态查询,从而降低了网络流量。 ​ ### Redis集群的分片原理 ​ 1. redis集群会将数据分散到16384个哈希槽中 2. 集群中每个节点负责一定范围的哈希槽 3. 在redis集群中,使用CRC16哈希算法计算键的哈希槽,以确定该键应该存储在哪个节点。 4. 16384=2的14次方 图示: <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/yV1yecYZWJvs6DQx.webp" alt="" width="100%" /> 每个节点会拥有一部分的槽位,然后对应的键值会根据其本身的key,映射到一个哈希槽中,其主要流程如下: 1. 根据键值的key,按照CRC16算法计算一个16bit的值,然后将16bit的值对16384进行取余运算,最后得到一个对应的哈希槽编号。 2. 根据每个节点分配哈希槽区间,对应编号的数据落在对应的区间上 ,就能找到对应的分片实例。 如下图: <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/ritTpqNQ1X3AGaEH.webp" alt="" width="100%" /> **注意**: redis的客户端可以访问集群中的任何一个节点,正常情况下,当前节点是包含这个数据的。如果,槽被转移了,客户端还未来得及更新槽的信息,当前实例没有这个数据,那么会返回MOVED响应给客户端,然后将客户端重定向到对应的实例。(这里的原因是,集群中每个节点,都保存有集群中完整的拓扑信息。) ​ ### redis集群中,请求key的详解实例: ​ 前提: redis客户端直接连接的并不是对应key的节点 假设: 客户端连接的是集群中的node1,但是需要访问的数据存储在node3中,键为user:1001 查询过程: 1. **计算哈希值**: 1. 客户端使用CRC16算法计算user:1001的哈希值,假设为12345 2. 计算哈希槽:12345%16384 = 12345 2. **查询请求**: 1. 因为客户端连接的是集群中的node1节点,所以客户端发送查询命令get user:1001到node1 3. **node1响应**: 1. node1检测到请求对应的user:1001属于node3, 2. node1给客户端返回一个moved错误,告诉客户端请求的键在另一个节点上。moved错误中会返回目标节点的信息。 4. **客户端重新连接**: 1. 客户端根据返回的目标节点信息,与新的节点node3进行连接 5. **再次发送查询请求** 1. 客户端向node3发送请求命令 get user:1001 6. **获得结果** 1. node3查询到user:1001的值,假设为{“name”:"凯歌","age":"18"},并返回结果给客户端。 ​ ### 为什么哈希槽个数是16384? ​ 1. 消息大小的考虑: 1. 正常的心跳包需要带上节点的完整配置数据,心跳是比较频繁的。所以需要考虑数据包的大小,如果使用16384数据包只需要2k,如果使用65535则需要8k 2. 实际上槽位信息使用了一个长度为16384位的数组来表示,节点拥有哪个槽位,旧件对应位置的数据信息设置为1,否则为0 3. 计算公式: 1. 16384/8/1024=2KB 2. 集群规模的考虑: 1. 一般集群的节点个数不会超过1000个,16384够用并且保证了每个分片上的槽的数量又不会太少。

八股:MySQL 中的 MVCC 是什么?(初版)

​ Mysql中的MVCC是什么? --------------- ### 阅读大纲 ​ <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/LSsfNtg2wzG0UrwH.webp" alt="画板" width="100%" /> ​ ### 对话解析 #### 话题:MVCC是什么? ​ **大佬:** 那么Mysql中的MVCC?你知道是啥不? **凯歌:** 那个啊,是这几个单词的简写,multi-version-concurrency-control,翻译成中文就是多版本并发控制。是一种并发控制机制。当有多个事务`同时对数据库进行读写`的时候,有了这个机制就可以让他们`一块执行,不用等待`。 ​ #### 话题:MVCC咋实现的,简单讲解 ​ **大佬:** 哎呦?这么厉害?它咋实现的啊?加锁? **凯歌:** 它呀,当多个事务同时读写数据库的时候,它给每个数据创建了一个数据快照,然后mysql那不会立即覆盖原有的数据,而是生成新的版本记录。每个记录都保留了`对应的版本号和时间戳`。 **凯歌:** 多个版本之间`串联`起来,就形成一条版本链,这样不同时刻启动的事务,可以`无锁`的获得,不同版本的数据(普通读)。此时读(普通读)写操作不会阻塞。 **凯歌:** 写操作可以继续写,无非就是会创建新的数据版本,但是只有在事务提交后,新版本才会对其他事务可见,未提交的事务修改,不会影响其他事务的读取。记录中的历史版本可供已启动的事务读取。 **凯歌:** 老样子,画个简图,给你个看看。 > 图内容简介: > > 1. 同一时刻,有三个事务,要对id=1的数据进行操作 > > 2. 第一个事务:新增id为1的数据,新增姓名为凯歌 > > 3. 第二个事务:修改id为1的数据,修改姓名为yes > > 4. 第三个事务:修改id为1的数据,修改姓名为鱼皮 > <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/KyJBCeTEZUE5k5C7.webp" alt="画板" width="100%" /> ​ #### 话题:版本链是什么? ​ **大佬:** 你这说的也太简单了,能不能深入说说啊,比如这个多版本,这个版本链到底是咋实现的? **凯歌:** 没问题,其实吧,这个MVCC里的多版本,并不是说真的存储了多个版本的的数据,**只是借助Undo Log**记录了每次写操作的反向操作,所以索引上对应的记录只会有一个版本,即最新版本。只不过可以根据Undo Log中的记录反向操作得到数据的历史版本,所以看起来是多个版本。 **凯歌:**上面这段说的有点多,我给你画个图,协助理解下。 ![画板](https://pic.code-nav.cn/post_picture/1673341687373500418/kOHTazBHpDeGVXCm.webp) **凯歌:** 这一块,比较难理解**,**下面我拿上面的insert语句,再进行一个举例。 **凯歌:** 当我们执行insert语句成功后,在数据页中,会新增一条id=1,name=凯歌的记录。 **凯歌:** 在id=1,name=凯歌的时候,还会记录一些别的信息,我们称为隐藏字段,这里我们在insert成功的同时,就还存储了`trx_id`和`roll_pointer`这两个隐藏字段。如下图: > * trx_id:当前事务ID > > * roll_pointer:指向Undo Log的指针 > <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/AXRIL6kLUCtgcqcH.webp" alt="画板" width="100%" /> **凯歌:** 如上图,此时插入的事务ID=1,此时插入,会生成一条Undo Log,并且记录上的roll_pointer会指向这条Undo Log,而这条Undo Log是一个类型为TRX_UNDO_INSERT_REC的log,代表是insert生成的。 **凯歌:** 里面存储了主键的长度和值(其他值,暂时不提),所以InnoDB,可以根据undo log里面的主键值,找到这条记录,然后把他删除,来实现回滚(复原)的效果。 **凯歌:** 因此可以简单的理解undo log里面存储的就是当前操作的反向操作,可以认为里面存储了一个delete 1; **凯歌:** 此时,事务ID=1的事务提交,然后,另一个事务ID=5的事务在执行update语句,更新id=1,name=yes,此时的记录和undo log 就如下图: <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/RUxLQR3VnM7nM8UO.webp" alt="画板" width="100%" /> **凯歌:** 正如上图,我们之前insert对应的undo log消失了。insert的事务提交了之后,对应的undo log就回收了。因此,再有事务来访问的时候,就不可能访问到比这个还要早的版本。 **凯歌:** 但是!update产生的undo log是不一样的,它的类型为,TRX_UNDO_UPD_EXIST_REC。 **凯歌:** 具体哪里不一样,我们继续往下看,当我们的事务ID=5,也就是刚才的update事务,提交后,来了另一个事务ID=11的事务来了,它要执行update语句,将id=1,name=yes的数据,改为id=1,name=鱼皮。那么他执行后现象,就如下图所示: <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/3CNUEu0PoEMohw1o.webp" alt="画板" width="100%" /> **凯歌:** 正如上图,这条update语句执行之后,我们之前产生的 undo log没有删除,依旧保留,因为可能会有别的事务,需要访问之前的版本,所以不能删除。 **凯歌:** 这样就形成了一个版本链,可以看到记录本身id=1,name=鱼皮,外加两条undo log ,这条id=1的记录,一共有三个版本。 ​ #### 话题:ReadView是什么? ​ **大佬:** 哦,这版本链,我明白了,但是这么多版本,一个事务过来了,它咋知道用哪个版本啊?你这也没说啊? **凯歌:** 说,这就说。要想知道,当前事务,需要的版本是哪个,我们就需要引入一个新的东西,叫ReadView。 **凯歌:** 这个ReadView吧,就是用来判断哪个版本对当前事务,是可见的。这里有四个概念,见下: > creator_trx_id:当前事务ID > > m_ids:生成readView时,还不活跃的事务ID集合,也就是已经启动,但还未提交的事务列表。 > > min_trx_id:当前活跃ID之中的最小值。 > > max_trx_id:生成readView时,InnoDB将分配给,下一个事务的,ID的值。(事务ID是递增分配的,越后面申请的事务ID就越大)。 **凯歌: 对于当前事务,可见版本的判断,是从最新版本开始,沿着版本链逐渐寻找老的版本,如果遇到符合条件的版本就返回。** **凯歌:** 判断条件如下: | TRX_ID(当前数据事务ID) | 是否可见 | 原因 | | --- | --- | --- | | trx_id == creator_trx_id | 可见 | 修改这条数据的事务,就是当前事务,所以可见 trx_id <min_trx_id | 可见 | 修改这条数据的事务,在当前事务生成readView的时候,已经提交,所以可见 min_trx_id =< trx_id < max_trx_id 且 trx_id 在 m_ids中 | 不可见 | 说民修改这条数据的事务,还没提交 min_trx_id =< trx_id < max_trx_id 且 trx_id 不在 m_ids中 | 可见 | 说明修改这条数据的事务,已经提交 trx_id >= max_trx_id | 不可见 | 说明修改这条数据的事务,在当前事务生成readView的时候,还没启动,所以不可见。(事务ID是递增的) | **凯歌:** 光看文字,没有效果,我们举个例子,练练手 ​ #### 举例:读已提交隔离级别下的MVCC ​ **凯歌:** 现在隔离级别是:**读已提交** > 背景描述: > > 假设,此时上文的: > > 事务ID=1的事务,已提交 > > 事务ID=5的事务,已执行 > > 此时: > > 事务ID=6的事务来了,状态是:未提交 > > (trx_id=6的事务要执行update table set name = '哈哈' where id = 2;) > > 现在,最大事务ID是6 > > 这时候: > > 有一个查询语句,开启了事务,语句为:select name from table where id = 1; > > 那么这个查询语句对应的上述四个概念的值,见下表 | 指标 | 值 | 原因 | | --- | --- | --- | | creator_id | 0 | 因为一个事务,只有当有修改操作的时候,才会被分配事务ID m_ids | [5,6] | 这两个事务都未提交,是活跃的 min_trx_id | 5 | max_trx_id | 7 | 因为当最大的事务ID是6,新来的时候,会递增,也就是7 | **凯歌:** 由于,trx_id=7的事务,要查找的是id=1的数据,所以需要先找到id=1的这条记录,此时的版本,如下图: ![画板](https://pic.code-nav.cn/post_picture/1673341687373500418/SSxK5Hr5tENzSO9s.webp) **凯歌:** 此时,id=1的数据的,最新版本记录上的trx_id=5,等于min_trx_id,且trx_id=5,在m_ids中,表明还是活跃的,未提交事务的,所以不可访问。 **凯歌:** 因为trx_id=5对应的数据,不可访问,所以,根据roll_pointer,我们找到了上图中的undo log **凯歌:** 在undo log中,trx_id=1,比min_trx_id还要小,说明在生成readView之前,就已经提交了。所以可以访问。 **凯歌:** 因此,trx_id=7的事务,会获得name=凯歌的结果。 > 事务ID=7的事务,拿到结果后,事务ID=5的事务,提交了 > > 此时,再次执行之前的SQL:select name from table where id = 1; > > 又会生成新的readView > > 同样,我们还是看那四个指标 | 指标 | 值 | 原因 | | --- | --- | --- | | creator_id | 0 | 没有修改操作,所以还是0 m_ids | [6] | 此时5已经提交了,所以只剩6了 min_trx_id | 6 | max_trx_id | 7 | 因为没有新的事物进来,所以还是7 | **凯歌:** 此时,根据SQL,我们还是查找的id=1的数据,所以当前版本还是跟刚才一样的图,如下: ![画板](https://pic.code-nav.cn/post_picture/1673341687373500418/VGaTPnlBz87UGIFE.webp) **凯歌:** **但是!这次数据中记录的的trx_id=5 小于min_trx_id,所以数据所在的最新版本,是可见的,所以,这次得到接结果是 name=yes!** **凯歌:** 这个,就是**读已提交的MVCC**,可以看到**一个事务**中的两次查询得到了**不同的结果**,所以也叫**不可重复读。** **凯歌: 这种两次读取得到的结果不一致的现象,我们称之为幻读。** * * * ​ #### 举例:可重复读隔离级别下的MVCC ​ **凯歌:现在隔离级别是:可重复读** **凯歌:** 可重复读和读已提交的MVCC版本判断的过程是一模一样的,我们就不再画了,这里说下两者之间的差别。 **凯歌:** 差别:生成readView | 隔离级别 | READVIEW生成规则 | | --- | --- | | 读未提交 | 每次查询,都会重新生成新的readView,每次查询的readView都用新的 可重复读 | 第一次查询生成readView后,后面每次查询都用的第一次生成的 | **凯歌:** 根据上述表格,我们可以知道,可重复读对比读已提交来说,差别就在第二次 **凯歌: 读已提交,两次查询,四个指标** > 读已提交,第一次查询 | 指标 | 值 | 原因 | | --- | --- | --- | | creator_id | 0 | 因为一个事务,只有当有修改操作的时候,才会被分配事务ID m_ids | [5,6] | 这两个事务都未提交,是活跃的 min_trx_id | 5 | max_trx_id | 7 | 因为当最大的事务ID是6,新来的时候,会递增,也就是7 | > 读已提价,第二次查询 | 指标 | 值 | 原因 | | --- | --- | --- | | creator_id | 0 | 没有修改操作,所以还是0 m_ids | [6] | 此时5已经提交了,所以只剩6了 min_trx_id | 6 | max_trx_id | 7 | 因为没有新的事物进来,所以还是7 | **凯歌: 可重复读,两次次查询,四个指标** > 可重复读,两次查询使用的都是第一次生成的,也就是下表 | 指标 | 值 | 原因 | | --- | --- | --- | | creator_id | 0 | 因为一个事务,只有当有修改操作的时候,才会被分配事务ID m_ids | [5,6] | 这两个事务都未提交,是活跃的 min_trx_id | 5 | max_trx_id | 7 | 因为当最大的事务ID是6,新来的时候,会递增,也就是7 | **凯歌:** 根据上述的分析过程,我们可以得到,可重复读,两次得到的查询结果都是一样的,均是:name=凯歌 ​ #### 话题:可重复读可以完全避免幻读的产生吗? ​ **大佬:** 哦哦哦,我明白了,读已提交,会产生幻读,而可重复读,不会产生幻读,所以我们应该用可重复读。 **凯歌:** 额,停,你说的有点问题,可重复读,其实并不能完全避免幻读。 **大佬:** 啊?我每次读都是第一个readView咋还会幻读? **凯歌:** 唉,这个就得给你再展开说下了。 **凯歌:** 可重复读,其实分为两种实现,第一种是我们上面说的那种,也成快照读,每次都是读第一次的readview,另外一种,是当前读,就是我们读取的时候,要保证数据是最新的。 ​ #### 话题:当前读是什么? ​ **凯歌:** 快照读,我们上面说的很清楚了,下面我讲下当前读。 **凯歌:**在可重复读的隔离环境下,有时候,我们也需要保证我们的数据是最新的,需要保证实时更新,比如,当我们要更新一条数据的时候,需要先去查询一下数据存在不存在,对不对?如果数据都不存在了,我们总不能也还是事务执行成功吧。 **凯歌:** 所以,当我们在执行一些需要确保数据一致性的操作的时候,就不能使用快照读,需要使用当前读。 > 一些使用当前读的场景 > > 1. select ..... for update > > 2. select .... lock in share mode > > 3. 插入、更新、删除操作 > > 4. 其他需要确保数据一致性的操作,例如:create table ....like > **大佬:** 那当前读,是怎么保证数据一致的啊?加锁? **凯歌:** 对的,当前读,会通过判断操作性质,给读取的数据加不同的锁,来保证数据的一致性,锁的一些例子,如下图: | 锁类型 | 适用操作 | 解释 | | --- | --- | --- | | 共享锁(S锁) | select .... lock in share mode | 允许多个事务读取同一行,但阻止其他事物,对其进行修改 排他锁(X锁) | select ... for update | 阻止其他事务读或者修改本行 间隙锁(Gap Lock) | select ... where column > value | 锁定锁记录之间的间隙,而不是具体的行,防止其他事务,在这些间隙中插入新的记录 Next-key | select ... where column > value | 行锁和间隙锁的结合体,不仅锁行记录,还锁间隙,可以防止其他事务,在该行之前或之后的间隙中插入新记录 等等 | | | **凯歌:** 说了这么多,我们再举个例子,演示下,可重复读隔离级别下出现幻读的现象 <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/wccRgUdkFl7mBEBP.webp" alt="画板" width="100%" /> **凯歌:** 由上图我们可以看到,事务A,有两条SQL > 隔离级别:可重复读 > > 事务A: > > 第一条SQL:InnoDB判断操作类型,执行快照读 > > 第一条SQL执行完毕,得到 1条数据,id=3 > > 事务B: > > 执行insert语句,并提交事务 > > 事务A: > > 第二条SQL:InnoDB判断操作类型,执行当前读 > > 第二条SQL执行完毕,得到 2条数据,id=3和id=4 **凯歌:** 根据上述描述,我们可以看到,在可重复读的隔离级别下,依旧产生了幻读。 **大佬:** 那这我咋避免啊? **凯歌:** 我们可以在事务A,开始执行的时候,直接执行 select ...for update。这样在事务A,开始的时候,就加了锁,其他事务也就新增不了,避免了幻读的产生。 **大佬:** 哦哦哦,明白了,但是感觉还有些细节,没太懂,你再给说下吧。 **凯歌:** 行,不过今天不行了,今天没时间了,在后续的文章中,我们再补充吧。 **大佬:** 你又来这招。 **凯歌:以上,就是我们本次关于MySQL中MVCC是什么?的全过程的讲解了,若有错误,请帮忙指出,一定修改。** * * *

小感悟: 小小的成就感: 写题解的第五天,今天的题目,写完解析之后,看了下,有2.5万字了已经,字数其实,并不代表什么,只不过,想想自己之前想写东西的时候,写个几百字都感觉很难,写不出来,现在虽然质量还不是很高,但是数量看着也很让人开心,嘿嘿。 最大的收货: 在加入训练营之前,没有怎么好好的从头到尾花好几个小时的时间去写一篇文章,所以在刚开始写的时候,哪怕自己心里规划了好多好多,然而写的时候还是乱七八糟,在这短短的五天里,从最开始的杂乱无章的写,到后面的列大纲写,再到后来的画思维导图写,最后到现在思维导图列大纲,对话式模拟写解析,通过观看别人怎么写,自己尝试不同的写作风格,慢慢的找到适合自己的写作风格,然后写出了自己现在感觉还可以的文章和样式,真的收获满满。 未来的期待: 希望自己可以圆满完成三十天打卡,找到一些志同道合的小伙伴,加油。

八股:MySQL 的 B+ 树中查询数据的全过程详解

​ MySQL 的 B+ 树中查询数据的全过程详解 ----------------------- ​ <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/GdklkNFBfBPP6zif.jpg" alt="画板" width="100%" /> ​ ### 对话解析 ​ **大佬:** 我刚才在用Mysql查数据,我知道这个数据是以B+树的形式存储的,那这个B+树里面,是咋找到我想要的数据的,你能给我详细讲讲不? **凯歌:** 没问题,老样子,我先上图,再解释。 **凯歌: 图一展示从根节点到叶子节点之间的过程** <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/V7Fo1sIwYE5xWlVu.webp" alt="画板" width="100%" /> **凯歌: 图二展示从叶子节点到数据的过程** <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/5J9riUFTzqhWqIEU.webp" alt="画板" width="100%" /> **凯歌:** 两个图看完后我们开始讲解详细过程 **凯歌:** 第一步:从根节点到开始,我们通过二分查找,找到我们要找的数据在哪一个区间,定位到下一阶的子节点位置 **凯歌:** 第二步:从内部节点开始,我们继续通过二分查找,找到我们要找的数据在哪一个区间,定位到下一阶的子节点位置 **凯歌:** 第三步:还是通过二分查找,我们找到叶子节点,所在的位置,然后进入叶子节点。 **凯歌:** 第四步:进入叶子节点后,我们先找到页目录,我们继续通过二分查找,找到数据所在的槽 **凯歌:** 第五步:找到槽后,比如我们想要找到主键为3的记录,我们可以知道,槽2可以调到数据4 **凯歌:** 第六步:记录是单项链表连接的,我们从槽2->主键4->主键3,这是行不通,这时,因为槽是连着的,所以我们可以得到槽1的位置 **凯歌:** 第七步: 得到槽1的位置后,我们通过槽1->主键2->主键3得到最终数据 **PS:实际上,每个分组的记录是有数量限制的,上面是简化** > * 第一个分组,只有一条记录 > > * 中间分组可以有4-8条记录 > > * 最后一条分组1-8条记录 > **大佬:** 哦哦哦,这么看,我就差不多了。 **凯歌:以上,就是我们本次关于MySQL 中B+树中查询数据的全过程的讲解了,若有错误,请帮忙指出,一定修改。**

八股:MySQL 中 varchar 和 char 有什么区别?

​ <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/UMIVeNLpcO2pNRVq.jpg" alt="画板" width="100%" /> ​ ### 对话解析 #### char和 varchar的区别 ​ **大佬:** 我今天遇到个新问题,Mysql建表的时候,那个char类型和varchar类型有啥区别的,不都是字符串吗? **凯歌:** 这俩啊,说的确实没问题,都是存字符串的,不过确实有点区别,我给你列个表,你直观感受下。 | 不同点 | CHAR | VARCAHR | | --- | --- | --- | | 存储方式不同 | 定长字符串(长度不足,空格补齐) | 变长字符串 占用空间不同 | 始终占用固定长度 | 需要多少,占用多少 性能不同 | 长度固定,可能会浪费空间 | 需要多少,存多少,可以节省一些空间,但是某些情况下,可能会影响性能 适用场景不同 | 适合存储固定且短的字符串 | 适合存储变化或较长的字符串 | **大佬:** 哦哦,看这个表格,我大概知道啥区别了,就是一个直尺,一个是卷尺呗?能变不能变的。 **凯歌:** 对对对,就是这意思 ​ #### varcahr的影响 ​ **大佬:** 那我看,你上面说,某些情况下,varchar会有影响,是啥情况,啥影响啊? **凯歌:** 哦哦哦,这个啊,这个是在排序或者分组的时候,产生的现象,我下面详细说说。 **凯歌:** 还是拿我们的user表举例子啊,比如,我们要查询user表中id在190万之后的用户的id,姓名,年龄,并且结果要按照年龄排序。SQL语句如下。 ```sql select id,name,age from user where id>1900001 order by age; ``` **凯歌:** 下面我画个图,我们这个SQL的执行流程,大概是什么样子啊。 <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/CjWSlaKSJFbbGJoQ.webp" alt="画板" width="100%" /> **凯歌:** 下面我我照着这个图挑重点讲讲 **凯歌:** 第一步:InnoDB执行我们的SQL语句,获取到未排序或分组的结果集。 **凯歌:** 第二步:判断sort_buffer(排序内存缓冲区)的大小,能不能放下我们的结果集 **凯歌:** 到这里,我们就需要暂停下,分析:怎么判断能不能放下 **凯歌: 很简单,一个是看下sort_buffer的大小,一个就是统计每个字段的长度,这时候,因为char存的都是固定且短的字符,而varchar存的是变化且长的字符,如果我们的varchar很长,就放不进sort_buffer** **凯歌:** 当我们不能把数据都放进内存里的时候,就需要把数据拆开 **凯歌:** 比如多路并归排序,这时候,我们原本可以一次在内存中操作完的,就需要多次IO操作,增加了成本,降低了性能。 > 多路并归排序: > > 将数据拆分成一块块可以放进sort_buffer中的大小,然后一块一块的进去排序,将排好序的数据,再存到磁盘中,等所有的小块,都排好了,再把每一个小块中,最小的主键取出来,然后再根据这些取出来的主键,把整个数据在内存排好,返回给客户端 **大佬:** 哦哦哦,那我大概明白了。这里的排序、分组啥的还能再展开讲讲不,感觉好多还可以展开再说说。 **凯歌:** 当然,不可以了! 时间来不及了,马上12点了,这块不是这里的重点,后期我在别的地方单独讲给你听。 **大佬:** 行吧,你个老六。 ​ #### 单个varchar的最大长度是多少? ​ **大佬:** 那不能展开讲那个,你说说一个varchar最多能存多少东西吧,你老说它可变,到底能变多长啊?无限长? **凯歌:** 这没问题。 **凯歌:** 根据mysql的官方文档,我们可以知道一个varcahr的最大长度是65535个字节。如下图: <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/ZdDvU079hh6YCsYT.png" alt="" width="100%" /> **大佬:** 这是它的整体大小,那我们存数据也能存这么多吗? **凯歌:** 不不,我们存数据的时候,会略微小一点。 **大佬:** 那为啥为小啊?存别的东西了?出轨了? **凯歌:** 对的,就是存别的东西了,不过没出轨啊,都是存的跟你相关的东西。 **凯歌:** 这额外存的东西,包含两部分,如下 > 第一部分:是否null值,占用1字节 > > 第二部分:字符串长度,占用1-2字节,以255为分界线,大于255字节,需要两个字节 **凯歌:** 也就是说,按照最小可用来说,我们可以存储:65535-1-2=65532字节 **大佬:** 哦哦哦,65532个字符嘛。 **凯歌:** 停!不是字符!是字节! **特别提示:字符和字节不同,不同的编码规则,一个字符对应的字节数不一致,例如:UTF-8编码规则,一个字符最大可以占用三个字节** **凯歌:以上,就是我们本次关于MySQL 中 varchar 和 char 有什么区别?的讲解了,若有错误,请帮忙指出,一定修改。**

八股:Mysql中 得索引数量是不是越多越好?为什么?

### 结论: * 不是越多越好,不论是从空间、时间、优化器选择、维护成本来说都不是越多越好。 ### 原因 <img src="https://pic.code-nav.cn/post_picture/1673341687373500418/peEdtFMKQmVLAj26.webp" alt="image.png" width="100%" /> #### 文字详解: #### 时间上: * **外部操作影响:** * 每当我们对数据做增、删、改操作得时候,索引需要跟着对应得修改,当索引数量很多时,对应的索引列都要改,增加耗时,降低性能。 * **内部变化影响:** * 当删除或增加数据的时候,索引列会采用分裂或合并的方式,来进行调整,索引列很多的时候,大量的合并和分裂行为,也会增加耗时,降低性能。 #### 空间上: * **空间占用大:** * 每创建一个索引,就需要新增一个B+树(B+树每个节点,默认占用16KB),建立的索引很多的时候,会占用很多磁盘空间,影响性能。 * **碎片化:** * 当进行了大量的增删操作时,索引可能会因为需要进行分裂或合并,从而产生碎片化,增加IO次数,降低性能 * 碎片化的索引还会导致更多的页缓存失效,进一步影响查询性能。(页缓存失效见扩展。) #### 优化器选择上: * **选择过多,耗时增加:** * mysql在执行我们的SQL语句之前会使用优化器进行优化,优化器需要评估所有的索引,来选出最优选,如果索引过多,会增加选择的耗时 * **错误选择:** * 当索引过多的时候,优化器的选择会更加的复杂,从而可能导致优化器选择了错误的次优选,导致性能降低。 * **放弃选择:** * 在下述几种情况中,优化器会选择放弃使用索引,转而使用全表扫描 * **评估不完:** 索引过多,优化器评估方案耗时太久 * **选择不出:** 索引过多,优化器找不到一个明显优于其他索引的方案 * **覆盖不足:** 索引很多,但都不符合 多列的查询条件,没有合适的联合索引。 * **选择性低:** 当索引很多,但是索引的选择性很低,比如Boolean类型的列 * **表的数据量小:** 当在小表中,优化器发现使用全表扫描的效果比使用索引还好 * **查询条件复杂:** 在复杂查询,特别是多个子查询的时候,虽然后很多索引,但是使用索引的并不能显著提高性能,反而会增加表的复杂性 * **索引竞争:** 在索引很多,且高并发的情况下,多个查询条件,可能会竞争同一个索引,导致锁争用或者IO瓶颈(详解见扩展) #### 维护成本上: * 过多的索引,会增加数据库的维护成本,包括备份,恢复,迁移等。 ### 扩展知识: #### 索引过多时,高并发场景下会出现锁争用和IO瓶颈,为什么?什么是锁争用? - **锁争用概念:** 并发场景下,多个线程争夺一个锁,称为锁争用 - **InnoD中锁争用的体现:** * InnoDB中对数据进行操作的时候,我们可以通过索引来快速定位到数据行。 * 因此索引是查找到数据的关键结构。 * 当我们在并发场景下,多个查询,并发访问同一个数据 * 就需要并发访问同一个索引 * 此时这种情况下,mysql就会对索引项加锁 * 而只有拿到这把锁的查询,才可以继续访问数据 * 所以这些并发查询就需要争抢这把锁,于是锁争用就出现了 - **InnoDB中锁争用的影响:** * 并发事务中,InnoDB通过行级锁来保证事务的隔离 * 当事务要对某一行数据进行改变的时候,会给当前行数据加上排他锁 * 防止同一时间,有其他事务,对同一数据进行修改 * 读取的时候,是共享锁 * 增删改的时候,因为索引是查询到数据的关键 * 所以也会对索引,添加排他锁 * **当有新的索引被删除或者添加时** * **索引页需要分裂或合并** * **此时事务会给分裂出的新的索引页** * **或要合并的相关的索引页** * **也加上锁** * **这就导致多个索引页同时被加锁** * **降低了性能** * **而索引越多,锁分裂和合并的也就越频繁,性能影响也就越大** #### **IO瓶颈:** * **合并和分裂影响:** * 索引越多,修改数据时,需要合并和分裂的索引就越多 * 合并和分裂越多,需要的IO操作就越多 * **缓存失效影响:** * 当索引越多,修改数据时,就需要频繁的修改缓存中的索引页 * 也就是说缓存中的索引页会频繁的失效 * 失效就需要更换新的 * 更换新的就需要IO操作 * **碎片化影响:** * 当索引越多,修改数据的时候 * 合并和分裂就越多 * 产生碎片化的概率就越大 * 索引碎片化的越多 * 需要的IO操作就越多 * 性能就越差

八股:Mysql的存储引擎有哪些?他们之间有什么区别?

<img src="https://pic.code-nav.cn/post_picture/1673341687373500418/WVVl4pLVyILtEl4L.webp" alt="](![image" width="100%" /> ### 扩展知识: #### 1.什么是存储引擎? **官方理解**: * 数据库中负责存储、管理、检查、维护数据的一个组件,定义了数据如何在磁盘或者内存中:存储、索引、更新、查找等具体实现方式、 **个人理解**: * 我们往仓库放东西,不同的东西,需要用不同的打包方式,不同的打包盒,然后放到不同的货架,然后打包盒和货架上需要贴不同的标签,这个打包盒、打包方式、货架、标签,这四步可以组成一个整体的打包过程,这个打包过程 需要一个高大上的名字 于是起名为:存储引擎 **个人理解和官方概念关联**: * 仓库-数据库:存储不同类型的数据(例如:用户信息、订单记录、日志等) * 打包盒-数据结构:不同的存储引擎使用不同的数据结构存储数据 * 打包方式-事务处理和锁机制:存储引擎决定如何安全的处理数据的插入、更新、删除操作 * 放置货物的区域-存储介质:存储引擎会将数据存放在不同的位置,例如:磁盘、内存, * 打包盒上标签和货架上标签-索引机制:存储引擎根据索引快速定位到数据存储在哪里 #### 2.为什么需要不同的存储引擎? **官方理解**: * 不同的存储引擎,有不同的特性,适用于不同的场景。 个人理解: * 就像我们的仓库,地方很大,我们的客户往仓库里存东西的时候,每个客户的东西是不一样的,那么不同的货物,需要不同的处理方式,以保证我们客户的货物,可以安全的存放,快速的拿取,同理,我们的应用程序就像客户,不同的应用程序,有不同的数据,我们数据库针对各种数据,需要有合适的存储和检索方式,于是我们需要不同的存储引擎 #### 3.怎么理解不同的存储引擎? 个人理解: * 把我们的数据库比作一个连锁超市,超市分:生鲜区、干货区、临时促销区、赠品区、收银台,除了线下的实体店,还有线上的app * 生鲜区(InnoDB):这里的商品(数据),需要特别的照顾,为了防止变质(数据损坏或丢失),所以这里配备了冷藏设备(事务支持)、保鲜膜(行级锁)和备用制冷器(崩溃恢复)。如果我们在生鲜区上架了商品(插入或更新),即使中途突然断电了(系统崩溃),那我们上架的商品,也不会坏掉(数据丢失)。 * 干货区(MyISAM):这里的商品(数据),不容易变质,所以可以大批量进货(批量插入数据),也因为不容易变质,所以货物放在一个普通的分类盒中(不支持事务),分类盒有一个整体的盖子(表级锁),买东西的时候,盖子被打开,可以很多人一块,批量的买(读取性能好),平时的时候,盖子是合住的,于是需要上货的时候,需要先打开盖子(表级锁),所以上货的时候比较慢(写性能慢) * 临时促销区域(Memory):这里的商品(数据)都放在收银台最近的桌子上(内存区),所以拿取的时候很快,但是超时今天关门后(系统重启)明天就没有了(数据不持久) * 赠品区(CSV):这里的商品都是用手提袋装好的(CSV文件),如果感觉当前存放的位置不合适,想放到别的地方,可以很简单的搬过去(导出到Excel或者其他程序) * 收银台(Archive):收银台里已完成的订单,已经存放起来,不再经常使用(归档数据),并且只能新增和查看,不能修改或删除 * 线上App(NDB):这是连锁超市的官方App,App里的商品,每个线下店(节点)里都有对应的商品(数据),并且他们之间实时同步。即使某个分店关门了,也可以从其他店拿取到商品。

下载 APP