1、go的map里边的value是否可以取地址操作
在 Go 中,你不能直接取 map 中元素的地址。这是因为:
Go 的
map是一个引用类型,其元素并不保证在内存中是稳定的。当 map 扩容或发生重新哈希时,其底层地址可能会变动。
⚠️ 错误示例:
m := map[string]int{"a": 1}
// 编译错误:cannot take the address of m["a"]
ptr := &m["a"]
✅ 正确做法(中转变量):
m := map[string]int{"a": 1}
val := m["a"]
ptr := &val // 拿的是副本的地址
fmt.Println(*ptr)
如果你要更新 map 中的值,建议直接操作 map,不要用地址。
💡 如果 value 是 struct,且你希望操作 struct 的字段怎么办?
建议将 value 改成指针类型:
type User struct {
Name string
Age int
}
m := map[string]*User{
"alice": {Name: "Alice", Age: 30},
}
m["alice"].Age = 31 // ✅ 这样是可以的,因为 value 本来就是指针
总结:
| 场景 | 能否取地址 |
|---|---|
| map[key] = 基本类型 | ❌ 不可取地址 |
| map[key] = struct | ❌ struct 本身不能取地址 |
| map[key] = *struct (指针) | ✅ 可以操作字段 |
2、什么是GMP,GMP的调度模型机制是什么
GMP 是 Go runtime 实现高性能并发的核心机制之一,是 Go 协程(goroutine)调度系统的基础架构。
🔍 什么是 GMP?
GMP 是 Go Runtime 的三大核心组件:
| 缩写 | 含义 | 说明 |
|---|---|---|
| G | Goroutine | 用户级轻量线程,是 Go 程序并发的基本单元 |
| M | Machine | 内核线程(由 OS 管理) |
| P | Processor | 执行上下文,调度器中的关键角色,控制着可同时运行的 G 数量(逻辑核心) |
🧠 GMP 调度模型原理
整体流程:M(内核线程)要运行 G(协程),必须绑定一个 P(处理器)。
1. 系统初始化
- 默认
GOMAXPROCS = NumCPU - 系统初始化时会创建相同数量的
P,比如 4 核就有 4 个P
2. 调度关系
- M 要运行 G,必须先获取一个 P
- P 中维护一个就绪 G 队列(run queue)
- M 取出 G 执行后,运行完会归还到 P 的队列或进入等待状态
3. 调度步骤(核心流程)如下:
[用户代码触发 Goroutine]
↓
放入 P 的本地 run queue
↓
M 获取 P,开始执行 G
↓
G 执行完后,如果有更多 G,就继续调度
🧩 其他重要机制
🔄 Work Stealing(工作窃取)
- 如果某个 P 的 G 队列空了,会从其他 P 窃取 G 保证负载均衡
🌐 Global Queue
- 如果 P 的本地队列满了,G 会被放到全局队列
- 新建 G 会优先加入本地队列,否则加入全局队列
💤 M 会睡眠和唤醒
- 没有可运行的 G,M 会睡觉(减少资源浪费)
- 有 G 加入队列时,唤醒休眠的 M
📌 总结图示
+-------+ +--------+ +---------+
| G | --> | run | --> | running |
+-------+ | queue | +---------+
^ ↓
P <- M (绑定后调度 G)
✅ 关键优势
- 极致轻量:G 的栈极小,可快速创建
- 高效调度:调度无需内核干预,比传统线程更轻盈
- 自动调优:随着负载动态调整 Goroutine 和线程数量
3、 什么是mysql的联合索引
✅ MySQL 的联合索引(Composite Index) 是一种索引类型,它包含多个列组成的索引。用于加速涉及多个列查询的执行效率。
📌 基本定义:
联合索引是一个索引结构中同时包含多个列。 比如建一个
(col1, col2, col3)的联合索引,就叫三列联合索引。
🔍 示例
CREATE INDEX idx_user_name_age ON users(name, age);
这个联合索引可以加速如下查询:
SELECT * FROM users WHERE name = 'Alice'; -- ✅ 使用索引
SELECT * FROM users WHERE name = 'Alice' AND age = 25; -- ✅ 使用索引
SELECT * FROM users WHERE age = 25; -- ❌ 无法使用(不满足最左前缀)
🧠 最左前缀原则(核心理解)
联合索引遵循 最左前缀匹配 原则:
联合索引 = (a, b, c)
有效索引前缀 =
- a
- a, b
- a, b, c
❗如果你跳过前缀中的某列(比如只用
b、c),就无法使用这个索引!
🔍 实用场景:
✅ 可使用联合索引:
WHERE a = ? -- ✅ 命中
WHERE a = ? AND b = ? -- ✅ 命中
WHERE a = ? AND b > ? -- ✅ 命中,范围查询后不能再用后续索引
❌ 不可使用联合索引:
WHERE b = ? -- ❌ 跳过最左字段
WHERE c = ? -- ❌ 同理
🎯 联合索引优点
- 降低多列查询的查询成本
- 减少排序、分组等操作的临时文件开销
- 能覆盖更多查询(搭配覆盖索引优化)
❗设计注意事项
- 列的顺序非常重要(频繁过滤的放前面)
- 切勿无脑建太多联合索引,会占用大量空间和写入性能
- 配合
EXPLAIN工具观察索引命中情况
3、 什么是mysql的覆盖索引
✅ MySQL 中的覆盖索引(Covering Index) 是指 查询中需要用到的所有列的数据都能从索引中直接获得,无需回表(访问表的原始数据行)。
📌 覆盖索引定义
覆盖索引:一个索引包含了查询所需要的所有列,因此查询时无需回表,提高效率。
🔍 示例:
假设有如下表和联合索引:
CREATE TABLE users (
id INT,
name VARCHAR(100),
age INT,
PRIMARY KEY(id)
);
CREATE INDEX idx_name_age ON users(name, age);
现在执行:
SELECT name, age FROM users WHERE name = 'Alice';
✅ 这个查询可以完全从 idx_name_age 索引中获取 name 和 age —— 就是覆盖索引!
🚀 为什么覆盖索引这么快?
因为 索引本身已经保存了需要的所有列值,所以:
- ❌ 不需要去访问表的数据页(即回表)
- ✅ 全部数据从索引结构中(如 B+ 树的叶子节点)直接读出来
- ✅ 极大减少了 IO 成本,查询效率高
⚠️ 什么是“回表”?
当你使用某个列的索引进行查询,但 SELECT 的字段中还有其他列未在索引中,那就需要从表里取完整数据,这叫 回表。
例子:
SELECT age FROM users WHERE name = 'Alice';
- 索引命中
name - 但
age不在idx_name索引中 - ✅ 命中索引查到行指针
- ❗ 然后需要“回表”去拿
age
✅ 如何使用覆盖索引?
- 查询字段都包含在索引中
- 查询条件使用了该索引
- 不使用
SELECT *❌(一定要明确字段)
🔧 实用技巧
- 联合索引中把 WHERE 和 SELECT 中都会用到的列放进索引
- MySQL 的 InnoDB 聚簇索引:主键索引天然是覆盖索引(因为数据和索引是一体的)
🧠 快速判断是否用了覆盖索引?
用 EXPLAIN 查看查询计划,如果看到:
key字段显示使用了某个索引Extra字段里有Using index
那就说明用了覆盖索引 ✅
4、什么是B+树?
📚 B+ 树(B+ Tree) 是一种多路平衡查找树,广泛用于数据库(如 MySQL 的 InnoDB)和文件系统中,特别适合 范围查询和磁盘读写优化。
✅ 核心定义
B+ 树是一种 N 叉平衡搜索树,所有数据都存储在叶子节点,内部节点只作为导航路径。
🌳 B+ 树 vs B 树 对比(关键差别)
| 特性 | B 树 | B+ 树(✅ MySQL 用这个) |
|---|---|---|
| 数据存储位置 | 所有节点 | 只在叶子节点存数据 ✅ |
| 范围查询效率 | 不高 | 叶子节点链表连接,范围查询高效 ✅ |
| 非叶子节点 | 存数据 + 索引 | 只存索引,不存数据 ✅ |
| 叶子节点间是否有指针 | ❌ 没有 | ✅ 有双向链表连接 |
🔧 结构图示(简化)
[17 | 35]
/ | \
[5 10] [20 30] [40 50] <-- 叶子节点,存储实际数据
↔ ↔ ↔ <-- 叶子节点通过链表连接(范围查询快)
🚀 为什么数据库喜欢用 B+ 树?
1️⃣ 避免大量磁盘随机 IO
- B+ 树的高度一般很小(2~4 层),查找路径短
- 每个节点页都和磁盘页大小对齐(如 16KB)
- 查找时只访问少量磁盘块,非常快
2️⃣ 更快的范围查找
- 叶子节点间通过链表连接
- 范围查询只要从一个叶子顺着链表走,非常高效
3️⃣ 更稳定的插入/删除效率
- 插入/删除节点后 B+ 树可以通过节点分裂/合并保持平衡
- 不会像红黑树一样频繁旋转
🧠 在 MySQL 中的使用场景
- InnoDB 的主键索引就是一棵 B+ 树(聚簇索引)
- **二级索引(普通索引)**也是 B+ 树,但叶子节点存的是
索引字段 + 主键值(而不是整行数据)
📌 总结一句话:
B+ 树是一种适合大规模数据检索和范围查找的磁盘友好型数据结构,MySQL 使用它是为了性能和效率的平衡。
5、mysql里边有几种锁
🔐 在 MySQL 中,锁(Locks)是数据库用于控制并发访问、保证数据一致性的核心机制。不同的存储引擎实现方式略有不同,但 InnoDB 是默认且最重要的引擎。
✅ MySQL 中常见的锁类型(按层级划分)
🔹 一、按作用对象粒度分类:
| 锁类型 | 描述 | 支持引擎 |
|---|---|---|
| 表级锁 | 锁定整张表,适用于读/写整表 | MyISAM, InnoDB |
| 行级锁 | 精确锁定某些行,提高并发 | ✅ InnoDB |
| 页级锁 | 锁定一页(多个行),介于行与表之间 | BDB(较老) |
🔹 二、InnoDB 的锁类型(💡重点)
✅ 1. 共享锁(S Lock)
- 允许多个事务读取同一行
- 不允许写
SELECT * FROM users WHERE id = 1 LOCK IN SHARE MODE;
✅ 2. 排他锁(X Lock)
- 当前事务独占该行:不能读、不能写
SELECT * FROM users WHERE id = 1 FOR UPDATE;
✅ 3. 意向锁(Intention Lock)
-
用于标识事务想要获取的行级锁类型
-
让表级锁和行级锁共存(不冲突)
-
分为:
- 意向共享锁(IS)
- 意向排他锁(IX)
💡 你不需要手动加意向锁,InnoDB 自动处理
🔹 三、其他特殊锁
🔸 Gap Lock(间隙锁)
- 锁住“范围”而不是具体行
- 防止“幻读”(插入新行)
SELECT * FROM users WHERE age > 30 FOR UPDATE;
🔸 Next-Key Lock
- Gap Lock + 行锁(前开后闭)
- 避免“幻读”,用于 Repeatable Read 级别
🔸 Auto-inc Lock(自增锁)
- 锁住自增主键生成器
- 多线程插入时保证自增值唯一
🔸 Metadata Lock(元数据锁)
- 锁住表结构(DDL操作期间)
- 防止 DDL 与 DML 冲突
🔹 四、MyISAM 的锁类型(只支持表级锁)
| 锁类型 | 是否阻塞写入 | 是否阻塞读取 |
|---|---|---|
| 读锁 | ❌ | ✅ 允许并发读 |
| 写锁 | ✅ 阻塞所有操作 | ✅ |
🧠 总结表格
| 分类 | 类型 | 特点描述 |
|---|---|---|
| 表级锁 | 读锁、写锁 | 粒度大、冲突多、开销小 |
| 行级锁 | S/X/Next-Key/GAP | 粒度小、并发高、开销大 |
| 意向锁 | IS、IX | 与表锁协作的辅助锁,不互斥行锁 |
| 特殊锁 | Metadata、自增锁 | 保证元数据/自增一致性 |
6、Redis的ZSet的作用和使用场景
🔢 Redis 的 ZSet(有序集合,Sorted Set) 是一个非常强大的数据结构,它结合了 集合的唯一性 和 列表的有序性。
✅ 基本定义:
ZSet 是一个不允许重复元素的集合,每个元素都关联一个 浮点型 score,Redis 会按 score 自动排序。
🔧 数据结构
- 元素:唯一字符串(value)
- 分值:
float64类型的 score(用于排序)
ZADD leaderboard 100 "Alice"
ZADD leaderboard 150 "Bob"
ZADD leaderboard 120 "Charlie"
这会构造如下的有序集合:
| Member | Score |
|---|---|
| Alice | 100 |
| Charlie | 120 |
| Bob | 150 |
📌 常用命令
| 命令 | 描述 |
|---|---|
ZADD key score member | 添加元素到 zset |
ZRANGE key start stop [WITHSCORES] | 正序取元素 |
ZREVRANGE key start stop [WITHSCORES] | 逆序取元素 |
ZSCORE key member | 查询某个成员的分数 |
ZRANK key member | 正序排名(0 开始) |
ZREVRANK key member | 倒序排名 |
ZREM key member | 删除某个成员 |
ZINCRBY key delta member | 分数增加 delta |
ZCOUNT key min max | 统计 score 在区间内的成员数量 |
ZRANGEBYSCORE key min max | 按分数范围取成员 |
🚀 使用场景
✅ 排行榜(游戏得分、网站积分系统)
ZADD leaderboard 1000 "player1"
ZINCRBY leaderboard 300 "player1" # 提升分数
ZREVRANGE leaderboard 0 9 WITHSCORES # Top 10 排名
✅ 延迟任务/定时队列
ZADD delayed_jobs 1650000000 "job123" # 分数是时间戳
ZRANGEBYSCORE delayed_jobs -inf 1650000050
✅ 热门文章 / 点赞计数
ZINCRBY hot_articles 1 "post:123"
🧠 内部结构
ZSet 使用两种结构实现:
- 跳表(SkipList):支持按 score 有序访问
- 字典(Hash Table):支持快速查找成员对应的分数
📌 总结
| 特性 | 支持 |
|---|---|
| 唯一性 | ✅ 每个 member 唯一 |
| 自动排序 | ✅ 根据 score 自动排序 |
| 范围查询效率 | ✅ log(N) 时间复杂度 |
| 使用内存 | 比普通集合多,但排序强大 🔥 |