查找和最小的 K 对数字
要点:最小堆int[]{数字,坐标},想成数组。【x,0】先加进去
class Solution { public List> kSmallestPairs(int[] nums1, int[] nums2, int k) { //最小堆 PriorityQueue pq = new PriorityQueue<>((a,b) -> a[0] - b[0]); for(int i = 0; i < Math.min(nums1.length,k);i++){ pq.add(new int[]{nums1[i] + nums2[0], i, 0}); } List> ans = new ArrayList<>(k); while(k-- > 0){ int[] top = pq.poll(); int i = top[1]; int j = top[2]; ans.add(List.of(nums1[i], nums2[j])); if(j+1 < nums2.length){ pq.add(new int[]{nums1[i] + nums2[j+1], i, j+1}); } } return ans; } }颠倒二进制位
要点:32位置的模拟
class Solution { public int reverseBits(int n) { int ans = 0; for(int i = 0; i < 32; i ++) { ans = n % 2 + 2 * ans; n = n>>1; } return ans; } }位1的个数
要点:n>>1
class Solution { public int hammingWeight(int n) { int ans = 0; while(n != 0){ if(n%2 == 1){ ans++; } n=n>>>1 ; } return ans; } }只出现一次的数字
要点:异或^
class Solution { public int singleNumber(int[] nums) { // int ans = 0; for(int num : nums){ ans ^= num; } return ans; } }只出现一次的数字 II
要点:位置上相加%3
class Solution { public int singleNumber(int[] nums) { //异或 %3 int ans = 0; for(int i = 0; i < 32; i++){ int cnt1 = 0; for(int num : nums){ //取第i位置 cnt1 += num >> i & 1; } ans = ans | ((cnt1 % 3) << i); } return ans; } }数字范围按位与
要点:抹平后续
class Solution { public int rangeBitwiseAnd(int left, int right) { int shift = 0; while(left < right){ left = left >> 1; right = right >> 1; shift++; } return right << shift; } }随机知识
📚 MySQL + Redis 面试核心知识点总结
一、MySQL 篇
1. 基础架构:一条 SQL 是怎么执行的?
text
客户端 → 连接器(管理连接/权限)→ 查询缓存(8.0已废弃)→ 解析器(词法/语法分析)→ 优化器(选择索引)→ 执行器(调用存储引擎接口)→ InnoDB
2. 存储引擎:InnoDB vs MyISAM
| 对比维度 | InnoDB | MyISAM |
|---|---|---|
| 事务 | ✅ 支持 | ❌ 不支持 |
| 行级锁 | ✅ 支持 | ❌ 只支持表锁 |
| 外键 | ✅ 支持 | ❌ 不支持 |
| MVCC | ✅ 支持 | ❌ 不支持 |
| 聚簇索引 | ✅ 是 | ❌ 否 |
| 适用场景 | 读写并发高、事务敏感 | 读多写少、非事务型 |
结论:MySQL 5.5+ 默认 InnoDB,几乎 90% 场景都用它。
3. 索引(重中之重)
3.1 B+Tree 索引为什么用 B+Tree 而不是 B-Tree 或二叉树?
B+Tree 非叶子节点不存数据,一个磁盘页能存更多索引,树更矮(IO 次数少)。
叶子节点用双向链表连接,支持范围查询和顺序遍历。
| 类型 | 叶子节点存什么 | 说明 |
|---|---|---|
| 聚簇索引 | 整行数据 | InnoDB 主键索引就是聚簇索引 |
| 非聚簇索引(二级索引) | 主键 ID | 查询非索引字段需要回表(再查一次主键索引) |
| 覆盖索引 | 查询所需字段直接在索引中,不回表 | 如select id,name where name='xxx'且name和id是联合索引 |
联合索引
(a, b, c)相当于三个索引:(a)、(a,b)、(a,b,c)。
where a=1 and b=2✅ 用索引where b=2 and c=3❌ 不走索引(跳过了 a)where a=1 and c=3→ 只走(a)部分
MySQL 5.6+ 引入。在存储引擎层就过滤掉不符合条件的记录,减少回表次数。
例子:
where a>1 and b=2,先通过a找到记录,在引擎层直接判断b=2,不符合的不回表。
| 情况 | 原因 |
|---|---|
like '%abc' | 以 % 开头,B+Tree 无法定位 |
or两边有非索引字段 | 整条 SQL 走全表扫描 |
| 对索引字段做函数/计算 | 如where date(create_time) = '2025-01-01' |
| 隐式类型转换 | 如varchar字段传int |
!=/<> | 无法用索引做等值查找 |
is null/is not null | 多数情况下失效 |
占用磁盘空间。
更新性能下降:每次
insert/update/delete,所有索引都要同步更新。
4. 事务与隔离级别
4.1 四大隔离级别| 级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ |
| READ COMMITTED | ❌ | ✅ | ✅ |
| REPEATABLE READ(MySQL默认) | ❌ | ❌ | ⚠️(部分避免) |
| SERIALIZABLE | ❌ | ❌ | ❌ |
MVCC(多版本并发控制):每一行数据有多个版本(通过隐藏字段
trx_id和roll_pointer指向 Undo Log)。不同事务读到不同版本的数据,实现非阻塞读。
快照读:
select(不加锁),读的是快照版本。当前读:
select for update/update/insert,读的是最新版本,并加锁。
5. 三大日志(MySQL 的“保险丝”)
| 日志 | 位置 | 作用 |
|---|---|---|
| Binlog(归档日志) | Server 层 | 记录所有变更(增删改),用于主从复制和数据恢复。逻辑日志(SQL 语句或行数据) |
| Redo Log(重做日志) | InnoDB 层 | 记录物理修改(“在 Page X 偏移 Y 处写入 Z”)。保证事务持久性(崩溃恢复)。WAL(Write-Ahead Logging)技术:先写日志,再写磁盘 |
| Undo Log(回滚日志) | InnoDB 层 | 记录修改前的版本,用于事务回滚和MVCC 快照读 |
text
Redo Log Prepare → 写 Binlog → Redo Log Commit
这是保证 MySQL 崩溃恢复时,主从数据一致的关键机制。
6. 锁机制
6.1 行锁分类| 锁类型 | 含义 |
|---|---|
| 记录锁(Record Lock) | 锁定单行记录 |
| 间隙锁(Gap Lock) | 锁定一个范围(不包含记录本身),防止幻读 |
| 临键锁(Next-Key Lock) | 记录锁 + 间隙锁,InnoDB RR 级别默认 |
6.2 死锁面试金句:MySQL 在 RR(可重复读)级别下,通过MVCC(快照读)+Next-Key Lock(当前读)解决了幻读问题。
产生条件:循环等待 + 互斥 + 不可抢占 + 请求与保持。
解决方案:按固定顺序访问资源;使用
SELECT ... FOR UPDATE时注意顺序;开启innodb_deadlock_detect死锁检测自动回滚。
7. 性能优化(Explain 解读)
Explain 结果中重点看什么?| 列名 | 关注点 |
|---|---|
| type | const>ref>range>index>ALL(全表扫描,要避免) |
| key | 实际使用的索引名,NULL代表没走索引 |
| rows | 预估扫描行数,越小越好 |
| Extra | Using filesort(需要额外排序,慢)Using temporary(用了临时表,慢)Using index(覆盖索引,快) |
8. 分库分表
| 策略 | 说明 |
|---|---|
| 垂直分库 | 按业务模块分(订单库、用户库) |
| 垂直分表 | 大字段拆出去,减少单表宽度 |
| 水平分表 | 按某个字段取模/范围分到多张表 |
| 分表中间件 | ShardingSphere-JDBC、MyCat |
二、Redis 篇
1. 为什么 Redis 这么快?
纯内存操作(读写都是内存,微秒级)。
单线程(6.0 之前):避免了上下文切换和锁竞争(但 6.0+ 多线程仅用于网络 I/O)。
I/O 多路复用(epoll):一个线程监听多个 Socket 连接。
高效的数据结构:SDS、跳表、压缩列表等。
2. 五大基础数据类型 + 底层实现(重点)
| 数据类型 | 用途 | 底层结构 |
|---|---|---|
| String | 缓存、计数器、分布式锁 | SDS(简单动态字符串) |
| Hash | 对象属性、购物车 | ziplist(元素少)/ hashtable(元素多) |
| List | 消息队列、最近 N 条 | quicklist(双向链表 + ziplist 混合) |
| Set | 去重、交并差 | intset(全整数少)/ hashtable |
| ZSet | 排行榜、延时队列 | ziplist(少)/ skiplist + dict(多) |
3. 持久化方式
| 方式 | 原理 | 优缺点 |
|---|---|---|
| RDB(快照) | 全量内存数据写入.rdb文件 | 恢复快,占用小;但可能丢失最后一次快照后的数据 |
| AOF(日志) | 记录每一条写命令 | 数据更完整(appendfsync always/everysec);文件大,恢复慢 |
| 混合持久化(4.0+) | RDB + AOF 混合 | 兼顾恢复速度和数据完整性 |
4. 过期策略 + 淘汰策略
4.1 过期删除策略| 策略 | 说明 |
|---|---|
| 惰性删除 | 访问时检查,过期才删除 |
| 定期删除 | 每 100ms 随机抽取一批带 TTL 的 key 检查并删除 |
| 主动淘汰(内存满时) | 见下方淘汰策略 |
maxmemory-policy)| 策略 | 说明 |
|---|---|
| noeviction | 不淘汰,直接返回错误 |
| allkeys-lru | 在所有 key 中淘汰最近最少使用 |
| volatile-lru | 在设置过 TTL 的 key 中淘汰 LRU |
| allkeys-random/volatile-random | 随机淘汰 |
| volatile-ttl | 淘汰过期时间最近的 key |
大厂主流选型:
allkeys-lru(追求命中率)或volatile-lru(只淘汰临时数据)。
5. 缓存三大问题 + 解决方案
5.1 缓存穿透(查不存在的 key)请求的 key 在 Redis 和 DB 都不存在,绕过缓存直接打 DB。
解决方案:
布隆过滤器(Bloom Filter):先判断 key 是否存在,不存在直接返回。
缓存空值:对不存在的 key 也缓存
null,并设短 TTL。
一个热点 key 过期瞬间,大量请求同时打到 DB。
解决方案:
互斥锁(SETNX):只允许一个线程去查 DB,其他等待/重试。
逻辑过期:Redis 不设物理过期,在 Value 里存过期时间戳,过期时异步更新,当前先返回旧数据。
多个 key 同时过期,或 Redis 宕机,大量请求直接打 DB。
解决方案:
过期时间加随机值:避免大量 key 同时过期。
多级缓存:本地缓存(Caffeine)+ Redis 两层。
熔断降级:Redis 挂了直接返回兜底数据,不打 DB。
Redis 高可用:主从 + 哨兵 / Cluster 集群。
6. 分布式锁(Redisson + 看门狗)
标准写法(原子性):
bash
SET lock_key unique_value NX EX 30
Redisson 的改进(看门狗 WatchDog):
默认锁有效期 30 秒,业务没执行完自动续期(每 10 秒续一次)。
防止业务执行时间长导致锁自动释放,引发并发问题。
底层依赖:lua脚本保证原子性(判断锁持有者 + 释放锁)。
7. 集群模式
| 模式 | 适用场景 | 特点 |
|---|---|---|
| 主从复制 | 读写分离 | 一主多从,主写从读。主挂需人工介入 |
| 哨兵模式(Sentinel) | 高可用 | 在主从基础上加哨兵,自动故障转移(选新主) |
| Cluster 集群 | 海量数据(> 10G) | 分片(16384 个槽位),多主多从。不支持跨槽位事务 |
三、面试综合追问:“MySQL 和 Redis 怎么配合使用?”
典型场景:读请求先查 Redis,命中则返回;未命中则查 MySQL,回写 Redis 并返回。
写请求:先更新 MySQL,再删除(或更新)Redis 缓存。
大厂选型:
强一致场景(转账、支付):走 MySQL,不用缓存。
读多写少场景(商品详情、文章):Redis + MySQL,允许短暂不一致。
最终一致方案:订阅 MySQL Binlog(如 Canal),异步更新 Redis,彻底解耦。
🎯 面试金句(背下来直接加分)
MySQL 索引:“B+Tree 的非叶子节点不存数据,叶子节点用链表相连,既支持等值查询,也支持范围查询,磁盘 IO 次数少。”
事务隔离:“InnoDB 在 RR 级别下,通过 MVCC 快照读 + Next-Key Lock 当前读,解决了幻读问题。”
Redis 快:“单线程 + 内存 + I/O 多路复用 + 高效数据结构,四者缺一不可。”
缓存三大问题:“穿透用布隆过滤器,击穿用互斥锁或逻辑过期,雪崩用过期随机值 + 熔断降级。”
分布式锁:“
SET NX EX+lua脚本保证原子性,Redisson 看门狗解决锁续期问题。”
把这份笔记刻进脑子里,MySQL + Redis 这块你就可以从容应对绝大多数面试了!如果某个具体点(如 Binlog 的三种格式、跳表原理、哨兵选举过程)想深挖,随时可以继续聊。
碎碎念:后续会更新每天学习的八股和算法 题,开始准备秋招的第58天。努力连续更新100天!以后每天就按,秋招项目【java +agent】,科研,必做项目,算法,八股,锻炼身体来总结。
总结:继续重新看八股了,加油加油
1.算法面试150 118/150 2h
2.秋招项目,【java 项目】,
【agent 项目 】,
3.科研要跑一下,
4.实习;7h
6.背八股,2h
7.锻炼身体,
总结:mysql和redis