腾讯 C++开发 一面
PCG
1. 自我介绍
2. 网络服务中大量 CLOSE_WAIT 和 TIME_WAIT 分别是什么原因,会导致什么问题,怎么解决
答案:CLOSE_WAIT 通常表示对端已经关闭连接,本端收到了 FIN,也回了 ACK,但本端应用层还没有调用 close()。如果大量 CLOSE_WAIT 堆积,一般说明服务端代码没有正确关闭 socket,可能是读到 0 后没有 close,异常分支漏 close,或者连接对象生命周期管理有问题。它会导致 fd 泄漏,最终可能出现 too many open files。
TIME_WAIT 一般出现在主动关闭连接的一方。它的作用是保证最后一个 ACK 能被对端收到,并让旧连接中的延迟报文在网络中自然消失。大量 TIME_WAIT 不一定是 bug,但如果短连接很多,会占用端口和连接表资源。
解决上,CLOSE_WAIT 要从代码里查连接关闭路径,保证读到 EOF、异常、超时、协议错误时都能释放 fd。TIME_WAIT 要看业务是否能复用长连接、连接池,减少频繁建连断连;必要时调整系统参数,但不能把系统参数当成根因解决。
代码:
ss -ant | awk '{print $1}' | sort | uniq -c
ss -antp | grep CLOSE-WAIT
lsof -p <pid> | wc -l
cat /proc/sys/fs/file-nr
3. 创建 socket 时常见参数有哪些,SO_REUSEADDR、SO_REUSEPORT、TCP_NODELAY 分别解决什么问题
答案:创建 TCP socket 一般会经历 socket()、bind()、listen()、accept()。socket(AF_INET, SOCK_STREAM, 0) 中,AF_INET 表示 IPv4,SOCK_STREAM 表示 TCP 字节流,第三个参数通常填 0 表示使用默认协议。
SO_REUSEADDR 主要用于允许地址复用,常见场景是服务重启时端口还处于 TIME_WAIT,设置后可以更快 bind 成功。SO_REUSEPORT 可以让多个进程或线程绑定同一个端口,由内核做连接负载分发,适合多进程网络服务。TCP_NODELAY 用来关闭 Nagle 算法,减少小包延迟,适合实时交互场景,但可能增加小包数量。
还要关注 SO_KEEPALIVE、收发缓冲区大小、非阻塞模式、listen backlog,这些参数都会影响服务在高并发下的行为。
代码:
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <unistd.h>
int createServerSocket(int port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
bind(fd, (sockaddr*)&addr, sizeof(addr));
listen(fd, 1024);
return fd;
}
4. TCP 监听队列里半连接队列和全连接队列有什么区别,backlog 参数到底控制什么
答案:TCP 服务端 listen 后,内核会维护连接相关队列。半连接队列存放已经收到 SYN、并回复 SYN+ACK,但三次握手还没完全完成的连接。全连接队列存放三次握手已经完成,但还没被应用层 accept() 取走的连接。
backlog 在不同内核版本里含义有细节差异,但实际排查时要同时关注半连接队列、全连接队列以及系统参数,比如 somaxconn、tcp_max_syn_backlog。如果应用层 accept 太慢,全连接队列可能溢出;如果遭遇 SYN flood,半连接队列可能被打满。
全连接队列溢出会导致客户端连接失败或重传,服务端可能看不到明显应用日志。排查时不能只看应用层 QPS,还要看内核统计。
代码:
sysctl net.core.somaxconn sysctl net.ipv4.tcp_max_syn_backlog ss -lnt netstat -s | grep -i listen
5. MySQL 中 B 树和 B+ 树有什么区别,为什么 InnoDB 索引常用 B+ 树
答案:B 树的非叶子节点和叶子节点都可以存数据,而 B+ 树通常只有叶子节点存完整数据,非叶子节点主要存 key 和子节点指针。B+ 树的叶子节点之间还有链表连接,非常适合范围查询,比如 between、order by、分页扫描。
InnoDB 使用 B+ 树,一个重要原因是磁盘 IO 友好。非叶子节点不存完整行数据,可以容纳更多 key,树高更低,查询时磁盘访问次数更少。另外叶子节点有序链表让范围查询效率更高。如果用普通二叉树或红黑树,树高太高,不适合磁盘页访问模型。
聚簇索引的叶子节点存整行数据,二级索引叶子节点存主键值,所以二级索引查到主键后,如果查询列不在索引里,还需要回表。
6. InnoDB 聚簇索引和二级索引有什么区别,为什么建议主键量自增
答案:InnoDB 的表数据本身按照主键组织,主键索引就是聚簇索引。聚簇索引叶子节点存放完整行数据。二级索引的叶子节点不直接存完整行,而是存索引列和主键值。如果通过二级索引查找非覆盖字段,需要先查二级索引拿到主键,再回到聚簇索引查整行,这就是回表。
主键建议尽量短、稳定、递增。短是因为所有二级索引叶子节点都会存主键,主键太大会放大索引体积。稳定是因为主键变化会导致数据位置变化。递增是因为随机主键会导致页分裂和页内碎片,影响插入性能。不过自增主键也有热点写入问题,在极高并发写入场景下需要结合业务分库分表或其他 ID 生成策略。
7. MySQL 联合索引的最左前缀原则是什么,哪些情况会导致索引失效
答案:联合索引比如 (a, b, c),查询条件要从最左边的 a 开始连续使用,才能较好利用索引。如果直接查 b 或 c,通常无法完整利用这个联合索引。遇到范围查询时,范围字段后面的字段一般不能继续用于有序定位,只能做过滤。
常见索引失效情况包括:对索引列做函数计算、隐式类型转换、前置模糊匹配、使用不符合索引顺序的条件、低选择度字段单独建索引、or 两侧索引不一致等。但“索引失效”不是绝对说法,最终还要看优化器选择,可能因为数据量小、选择度差,优化器认为全表扫描更便宜。
代码:
CREATE INDEX idx_user_scene_time ON t_event(user_id, scene_id, create_time); -- 可以较好利用 user_id、scene_id、create_time SELECT * FROM t_event WHERE user_id = 1001 AND scene_id = 8 AND create_time >= '2025-01-01'; -- 不符合最左前缀,通常无法有效使用该联合索引 SELECT * FROM t_event WHERE scene_id = 8; -- 对索引列做函数,可能导致无法走索引 SELECT * FROM t_event WHERE DATE(create_time) = '2025-01-01';
8. MySQL 慢查询怎么排查,EXPLAIN 里重点看哪些字段
答案:慢查询排查一般先打开慢查询日志,确认 SQL、耗时、扫描行数和执行频率。然后用 EXPLAIN 看执行计划,重点关注 type、key、rows、Extra。type 越接近 const/ref/range 通常越好,如果是 ALL 就是全表扫描。key 表示实际使用的索引,rows 是优化器估算扫描行数,Extra 里如果出现 Using filesort、Using temporary,要重点关注排序和临时表成本。
慢查询不一定只是没索引,也可能是索引选择度太差、返回数据太多、分页 offset 太大、锁等待、buffer pool 命中率低、SQL 写法导致无法走索引。优化时不能只靠感觉加索引,要结合数据分布和执行计划。
代码:
EXPLAIN SELECT id, title FROM t_article WHERE author_id = 100 AND status = 1 ORDER BY publish_time DESC LIMIT 20; SHOW VARIABLES LIKE 'slow_query_log'; SHOW VARIABLES LIKE 'long_query_time'; SHOW PROCESSLIST;
9. MySQL 中 MVCC 是怎么实现的,快照读和当前读有什么区别
答案:InnoDB 的 MVCC 依赖隐藏字段、undo log 和 ReadView。每行记录里会有事务 ID 和回滚指针,回滚指针指向 undo log 中的历史版本。快照读执行时会生成 ReadView,根据活跃事务列表、最小活跃事务 ID、下一个事务 ID 等信息,判断某个版本对当前事务是否可见。如果当前版本不可见,就沿着 undo log 找更老的版本。
普通 select 通常是快照读,不加锁,读的是符合可见性规则的历史版本。select ... for update、up
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏系统梳理C++方向, 大中厂高频高频面试考点 , 内容皆来自真实面试经历,从基础语法、内存管理、STL与设计模式,到操作系统与项目实战,结合真实面试题深度解析,帮助开发者高效查漏补缺,提升技术理解与面试通过率,打造扎实的C++工程能力.
查看2道真题和解析