「跳槽必备」Golang面试必考题合集

86 阅读10分钟

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 的三大核心组件:

缩写含义说明
GGoroutine用户级轻量线程,是 Go 程序并发的基本单元
MMachine内核线程(由 OS 管理)
PProcessor执行上下文,调度器中的关键角色,控制着可同时运行的 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

❗如果你跳过前缀中的某列(比如只用 bc),就无法使用这个索引


🔍 实用场景:

✅ 可使用联合索引:

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 索引中获取 nameage —— 就是覆盖索引


🚀 为什么覆盖索引这么快?

因为 索引本身已经保存了需要的所有列值,所以:

  • ❌ 不需要去访问表的数据页(即回表)
  • ✅ 全部数据从索引结构中(如 B+ 树的叶子节点)直接读出来
  • ✅ 极大减少了 IO 成本,查询效率高

⚠️ 什么是“回表”?

当你使用某个列的索引进行查询,但 SELECT 的字段中还有其他列未在索引中,那就需要从表里取完整数据,这叫 回表

例子:

SELECT age FROM users WHERE name = 'Alice';
  • 索引命中 name
  • age 不在 idx_name 索引中
  • ✅ 命中索引查到行指针
  • ❗ 然后需要“回表”去拿 age

✅ 如何使用覆盖索引?

  1. 查询字段都包含在索引中
  2. 查询条件使用了该索引
  3. 不使用 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"

这会构造如下的有序集合:

MemberScore
Alice100
Charlie120
Bob150

📌 常用命令

命令描述
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) 时间复杂度
使用内存比普通集合多,但排序强大 🔥