RSS
菜单
全部文章快讯开发科技深度热点

第 30 章 腾讯系MySQL真题精讲

内容摘要

腾讯系(社交/游戏/云/内容)面试的 MySQL 题偏"海量数据怎么存"——好友关系、消息流、聊天记录、游戏资产、日志埋点都是它的高频场景。本篇精选 20 道公开高频题型,覆盖关系表设计、Feed 流、资产并发扣减、冷热分离、写后读一致性、大字段、不停机迁移、跨机房容灾等。题目均为公开面经常见类型,解析讲通用原理,不涉及任何公司未公开信息。前置知识:第 9、15、17、19 章;本系列示例基于 MySQL 8.0。

📖 本篇简介:腾讯系(社交/游戏/云/内容)面试的 MySQL 题偏"海量数据怎么存"——好友关系、消息流、聊天记录、游戏资产、日志埋点都是它的高频场景。本篇精选 20 道公开高频题型,覆盖关系表设计、Feed 流、资产并发扣减、冷热分离、写后读一致性、大字段、不停机迁移、跨机房容灾等。题目均为公开面经常见类型,解析讲通用原理,不涉及任何公司未公开信息。前置知识:第 9、15、17、19 章;本系列示例基于 MySQL 8.0。

目录

  • 1 本篇考情与知识地图
  • 题目 1:海量好友关注关系表怎么设计
  • 题目 2:社交消息Feed时间线怎么存
  • 题目 3:游戏道具资产怎么设计与并发扣减
  • 题目 4:海量聊天记录存储方案
  • 题目 5:大表水平拆分后历史数据怎么归档
  • 题目 6:高并发下自增主键有什么瓶颈
  • 题目 7:redo undo binlog在高并发下怎么调
  • 题目 8:有了Redis缓存为什么还要MySQL
  • 题目 9:海量日志埋点数据怎么存
  • 题目 10:冷热数据如何分层存储
  • 题目 11:写后立刻读主从延迟怎么解决
  • 题目 12:TEXT BLOB大字段怎么存
  • 题目 13:登录会话放DB还是缓存
  • 题目 14:分表后数据怎么迁移不停机
  • 题目 15:MySQL80比57在工程上收益在哪
  • 题目 16:游戏排行榜用ZSET还是DB
  • 题目 17:长连接服务和数据库连接池什么关系
  • 题目 18:数据量大时count慢怎么优化
  • 题目 19:软删除和唯一索引冲突怎么办
  • 题目 20:跨机房异地多活数据怎么复制
  • 速查速记
  • 常见坑与注意事项
  • 复习清单
  • 练习题

1 本篇考情与知识地图

腾讯系面试 MySQL 喜欢从"社交/游戏数据模型"切入,核心是量级大、写多读多、关系复杂。先看这张分工表:

| 数据类型 | 典型场景 | 存储选择 |

|---|---|---|

| 关系数据 | 好友、关注、拉黑 | MySQL 关系表 + 缓存 |

| 时间线 Feed | 好友动态、群消息 | 推/拉模式 + 存储分离 |

| 资产数据 | 游戏道具、金币 | MySQL 事务 + 流水 |

| 海量消息 | 聊天记录 | 分表 + 冷数据归档 |

| 计数排行 | 点赞、排行 | Redis ZSET |

| 日志埋点 | 行为上报 | 不进 MySQL,走数仓 |

一句话主线:关系和资产进 MySQL,热度和计数进缓存,海量日志进数仓,别什么都塞 MySQL。

题目 1:海量好友关注关系表怎么设计

考点

  • 关系表一行一条、双向索引;
  • 双向关系冗余 vs 反查;
  • 分页与去重。

参考答案

1. 关系表一行一条:(user_id, followee_id, created_at),主键或唯一索引 (user_id, followee_id);

2. 查"我关注谁":按 user_id 索引查;查"谁关注我":按 followee_id 建反向索引;

3. 好友(双向):两条关注记录互相命中,或单独维护好友表;

4. 分页:按 created_at 或 ID 游标翻页,避免 OFFSET 深翻;

5. 数据量大:按 user_id 分表,关系落个人分片。

-- 关注关系表,本示例基于 MySQL 8.0
CREATE TABLE t_follow (
  user_id BIGINT NOT NULL,        -- 关注者
  followee_id BIGINT NOT NULL,    -- 被关注者
  created_at DATETIME DEFAULT NOW(),
  PRIMARY KEY (user_id, followee_id),   -- 防重复关注
  KEY idx_followee(followee_id)   -- 反向查粉丝
);

追问

  • 问:要不要冗余一份粉丝表?答:小体量用反向索引查即可,大体量高频可冗余粉丝表,接受异步同步。
  • 问:怎么判断是否互相关注?答:查两条记录是否同时存在,或加 is_friend 标记位。

题目 2:社交消息Feed时间线怎么存

考点

  • 推模式 / 拉模式 / 推拉结合;
  • 存储分离。

参考答案

1. 推模式(写扩散):发动态时主动写进每个粉丝的收件箱,读快但大V粉丝多写放大;

2. 拉模式(读扩散):发动态只存自己发件箱,读时拉关注列表再聚合,读慢;

3. 推拉结合:普通用户推、大V拉,平衡写放大与读延迟;

4. 存储:收件箱/发件箱用缓存 + 分表,动态正文存 MySQL/对象存储;

5. 时序:按时间倒序游标分页。

追问

  • 问:大V发一条要写几百万粉丝怎么办?答:大V走拉模式,粉丝读时实时聚合,或分组批量推。
  • 问:已读未读怎么处理?答:不存逐条已读,存"用户上次拉取时间"即可。

题目 3:游戏道具资产怎么设计与并发扣减

考点

  • 事务一致性;流水与余额。

参考答案

1. 资产表 + 流水表:资产表存余额,流水表记录每笔变动,便于对账;

2. 扣减原子化:UPDATE asset SET count=count-? WHERE uid=? AND item=? AND count>=?;

3. 同事务:扣减与写流水在一个本地事务里,要么都成功;

4. 防并发超扣:靠条件 count>=? 或乐观锁版本;

5. 异步发奖:活动发奖先写流水异步入账,削峰。

-- 资产扣减与流水同事务,本示例基于 MySQL 8.0
START TRANSACTION;
UPDATE t_asset SET item_cnt = item_cnt - 1
 WHERE uid = 1001 AND item_id = 88 AND item_cnt >= 1;  -- 不足则0行
INSERT INTO t_asset_log(uid, item_id, change_cnt, balance, created_at)
VALUES (1001, 88, -1, (SELECT item_cnt FROM t_asset WHERE uid=1001 AND item_id=88), NOW());
COMMIT;

追问

  • 问:流水表会很大吗?答:会,按时间分表/归档;余额是最终态,流水只对账用。
  • 问:发奖失败要回滚吗?答:活动类用异步补偿,保证最终入账。

题目 4:海量聊天记录存储方案

考点

  • 分表键;冷热分离;归档。

参考答案

1. 按会话分表:会话 ID 哈希分表,单会话消息落固定分片;

2. 冷热分离:近期消息热存,历史消息归档;

3. 消息 ID 时序化:消息 ID 含时间戳,支持按时间拉取;

4. 读多:群消息热点可挡 Redis;

5. 合规留存:按法规保留期限,到期归档/删除。

追问

  • 问:群消息和单聊表结构一样吗?答:可统一成"会话消息表",会话类型区分;群聊广播是另一回事。
  • 问:历史消息怎么查?答:归档库只读查询,别让在线库扛历史扫描。

题目 5:大表水平拆分后历史数据怎么归档

考点

  • 归档流程;可恢复。

参考答案

1. 按时间窗口归档:如只保留近 6 个月在线,更早的迁归档库;

2. 迁移 + 校验 + 删除:先搬、校验行数/checksum 一致再删在线数据;

3. 按分区 DROP:分区表直接 DROP 老分区,比 DELETE 快;

4. 归档库可查:提供只读入口;

5. 备份兜底:归档前再备份一次。

追问

  • 问:DELETE 删历史数据行不行?答:大事务、慢、产生大量 undo/ binlog,优先 DROP 分区或分批删除。
  • 问:归档删错怎么办?答:先迁移到归档库验证无误,删前备份,保留回滚窗口。

题目 6:高并发下自增主键有什么瓶颈

考点

  • 自增锁;主从切换。

参考答案

1. 5.7 及以前:自增用表级自增锁,并发插入有竞争;8.0 改为轻量 mutex,大幅缓解;

2. 主从切换:新主自增起点若没处理可能冲突,靠 auto_increment_increment/offset 协调;

3. 批量插入:INSERT ... VALUES (...),(...) 一次分配一批自增值,减少竞争;

4. 分库后:自增失去全局唯一性,需分布式 ID;

5. 有序写入:自增主键顺序插入,B+ 树尾部写入,性能好。

追问

  • 问:8.0 自增怎么改的?答:把表锁改成内存 mutex,且自增值持久化到 redo log,重启后不复用。
  • 问:自增会用完吗?答:BIGINT 足够,INT 才要担心。

题目 7:redo undo binlog在高并发下怎么调

考点

  • 三种日志职责;参数方向。

参考答案

1. redo log:保证 crash-safe,innodb_log_file_size 适中——太小导致频繁 checkpoint 刷盘,太大恢复慢;

2. undo log:保证回滚与 MVCC,长事务不提交会让 undo 膨胀、purge 跟不上;

3. binlog:主从复制依据,sync_binlog=1 最安全但略慢;

4. 关键:缩短事务、别长事务占着 undo;

5. 刷盘策略:按可靠性要求平衡 innodb_flush_log_at_trx_commit 与 sync_binlog。

追问

  • 问:双 1 是什么?答:innodb_flush_log_at_trx_commit=1 + sync_binlog=1,最安全。
  • 问:undo 膨胀症状?答:表空间暴涨、purge 延迟、历史读视图堆积。

题目 8:有了Redis缓存为什么还要MySQL

考点

  • 缓存与存储边界。

参考答案

1. MySQL 持久化 + 事务 + 复杂查询,Redis 是内存易失、不适合做权威存储;

2. Redis 会宕机/被清,重启后必须从 MySQL 重建;

3. MySQL 支持复杂条件查询、Join、事务约束,Redis 数据结构有限;

4. 定位:Redis 是"加速层",MySQL 是"数据源";

5. 成本:全量数据放内存太贵,MySQL 承担大容量持久数据。

追问

  • 问:什么数据可以只在 Redis?答:可重建的临时态(会话、计数),权威数据必须回 MySQL。
  • 问:Redis 缓存预热?答:上线/大促前把热点数据刷进缓存。

题目 9:海量日志埋点数据怎么存

考点

  • MySQL 不适合日志;
  • 数据流选型。

参考答案

1. 别进 MySQL:埋点写 QPS 极高、分析型查询多,MySQL 扛不住也不擅长;

2. 链路:埋点 → 消息队列(Kafka)→ 流式计算/数仓(ClickHouse/Hive)→ 大屏/报表;

3. MySQL 只存:需要事务、点查、订单态的核心业务数据;

4. 实时与离线分离:实时入 ClickHouse,离线落 Hive;

5. 成本:日志保留周期短,按冷热分层降本。

追问

  • 问:为什么不能直接写 MySQL?答:写吞吐、聚合分析、存储成本都不匹配。
  • 问:少量关键行为要回查业务库?答:业务库只存必要状态,明细走数仓关联。

题目 10:冷热数据如何分层存储

考点

  • 分层策略;查询路由。

参考答案

1. 热数据:在线库 + 缓存,低延迟点查;

2. 温数据:略便宜的存储/只读副本;

3. 冷数据:归档库/列存/对象存储,按需查询;

4. 路由:按时间自动路由到对应层,业务无感;

5. 生命周期:自动迁移、到期销毁。

追问

  • 问:冷数据还要支持随机查吗?答:低频即可,慢一点可接受;高频就还得留在线。
  • 问:怎么判定冷热?答:按访问时间/频率统计,自动下沉。

题目 11:写后立刻读主从延迟怎么解决

考点

  • 一致性读策略。

参考答案

1. 写后读强制走主库:如刚发动态立刻看自己动态;

2. 基于写请求的会话粘滞:写后短时间内该请求/用户走主;

3. 半同步复制:主库提交等从库 ACK;

4. 缓存写后更新:读走缓存绕开从库;

5. 延迟监控切流:从库延迟大时读切回主。

追问

  • 问:全走主库不就好?答:主库扛不住所有读,只对"必须强一致"的读走主。
  • 问:怎么知道该走主?答:业务上标注写后读接口,路由层识别。

题目 12:TEXT BLOB大字段怎么存

考点

  • 行格式与溢出页;
  • 大字段拆分。

参考答案

1. 大字段单独表:主表存常用字段,TEXT/BLOB 拆扩展表,按需加载,避免拖慢主表扫描;

2. 用覆盖索引:主表小列适合索引和缓存;

3. 正文存对象存储:长文本/图片放 OSS,DB 只存 URL;

4. 避免 SELECT *:别把大字段带出来;

5. 行格式:DYNAMIC 格式大字段存溢出页,行内只留指针。

追问

  • 问:TEXT 占行空间吗?答:DYNAMIC 下只存前 768 字节指针,主体溢出页,不影响行内。
  • 问:大字段影响索引吗?答:不直接,但宽表回表代价大,拆表后覆盖索引更高效。

题目 13:登录会话放DB还是缓存

考点

  • 会话存储选择。

参考答案

1. 会话状态放 Redis:高频读写、可过期、易失可接受;

2. DB 只存:账号、凭证、需要持久化的授权记录;

3. JWT:无状态 token,服务端不存会话;

4. 别把会话强塞 MySQL:高频读写下 MySQL 压力大,且过期清理麻烦;

5. 登录态过期:Redis TTL 天然支持。

追问

  • 问:Redis 丢了会话怎么办?答:会话可重建(重新登录),不是权威数据。
  • 问:踢下线怎么做?答:删 Redis key 或 token 版本号校验。

题目 14:分表后数据怎么迁移不停机

考点

  • 双写 + 追平 + 校验。

参考答案

1. 新表建好多分片;

2. 双写:新写同时写新旧表;

3. 存量迁移:离线工具分批搬运老表数据;

4. 增量追平:订阅老表 binlog 回放新表;

5. 校验 + 切读切写:行数/checksum 一致后灰度切流,老表只读过渡。

追问

  • 问:迁移期间数据不一致?答:以老表为准,binlog 持续追平,切流前全量校验。
  • 问:分批迁移多大一批?答:每批几千到一万,避开高峰,限速。

题目 15:MySQL80比57在工程上收益在哪

考点

  • 8.0 关键特性。

参考答案

1. 原子 DDL:DDL 要么全成功要么回滚,不再中途崩溃留残表;

2. 窗口函数 / CTE:复杂查询不用写一堆自连接;

3. 不可见索引、函数索引:灰度验证索引更安全;

4. 隐藏列、降序索引:排序方向更灵活;

5. 性能优化:内部临时表、JSON、优化器改进;

6. 自增持久化:重启不复用自增值。

追问

  • 问:5.7 升 8.0 要注意什么?答:字符集默认 utf8mb4、sql_mode 更严、保留字变化,先在测试环境全量回归。
  • 问:8.0 备份用什么?答:mysqldump/xtrabackup,注意数据字典变更。

题目 16:游戏排行榜用ZSET还是DB

考点

  • 排行榜数据结构;实时性。

参考答案

1. Redis ZSET:天然支持分数排序、取 TopN、查名次,O(logN),适合实时榜;

2. DB 只存:历史快照、最终结算;

3. 定期落库:榜单结束后把结果刷 MySQL 持久化;

4. 量大:大榜分片/拆榜,别一个 ZSET 塞全服;

5. 持久化兜底:Redis 开启 RDB/AOF,宕机可恢复。

追问

  • 问:实时更新分数频繁写 ZSET 扛得住吗?答:Redis 单线程内存操作很快,扛得住高频。
  • 问:并列名次怎么算?答:ZSET 按 score,名次用 ZREVRANK,并列需业务层处理。

题目 17:长连接服务和数据库连接池什么关系

考点

  • 连接复用;别把长连接和 DB 连接混淆。

参考答案

1. 长连接是客户端与网关之间的 TCP 连接,目的是省握手、实时推送;

2. DB 连接是网关/应用到 MySQL 的连接,数量受 max_connections 限制;

3. 二者独立:百万长连接不能变成百万 DB 连接;

4. 应用侧连接池复用:少量 DB 连接服务大量长连接请求;

5. 异步非阻塞:IO 多路复用让少量 DB 连接扛住大量业务请求。

追问

  • 问:长连接越多 DB 连接要越多吗?答:不要,请求是排队复用连接池的。
  • 问:DB 连接池满了?答:调大上限 + 缩短事务 + 排队超时。

题目 18:数据量大时count慢怎么优化

考点

  • count 原理;估算与替代。

参考答案

1. InnoDB 的 `count(*)` 要扫数据(无 where 时),越大越慢;

2. `count(*)` 带 where 也走索引/全扫;

3. 优化:用 COUNT(*) 走最小索引、近似值用 SHOW TABLE STATUS 估算;

4. 计数表:单独维护计数表,增删时同步更新;

5. 大数:把计数放 Redis,定期回写 DB。

-- 走最小二级索引减少回表,本示例基于 MySQL 8.0
SELECT COUNT(*) FROM t_order WHERE status = 1;  -- 保证(status)有索引

追问

  • 问:MyISAM 的 count 为什么快?答:它内部存了行数,InnoDB 因为 MVCC 要数可见行。
  • 问:业务要精确总数怎么办?答:维护计数表,别每次 count 大表。

题目 19:软删除和唯一索引冲突怎么办

考点

  • 唯一约束与删除标记。

参考答案

1. 问题:用户名要求唯一,软删除后留了 is_deleted=1,再建同名用户冲突;

2. 方案一:唯一索引带上删除标记 (username, is_deleted)——但多次删除会重复;

3. 方案二:软删除时把 username 拼接删除时间戳/ID,释放原名;

4. 方案三:唯一索引列里存"删除后的 tombstone",未删除时用真名;

5. 权衡:接受"删除后名字不可复用"也可,看业务。

追问

  • 问:最常用哪种?答:唯一索引 = (username, deleted_at),未删时 deleted_at 为 NULL,删除时填时间。
  • 问:NULL 在唯一索引里?答:MySQL 允许多个 NULL,所以未删时不冲突。

题目 20:跨机房异地多活数据怎么复制

考点

  • 同步 vs 异步复制;冲突处理。

参考答案

1. 主从异步复制:跨机房延迟大,不能强一致;

2. 单元化:按用户单元路由,请求落在本单元,跨单元访问走中心;

3. 双向复制:多主会冲突,要靠单元化避免同一条数据两边写;

4. 容灾 RPO:异步复制有数据丢失窗口,关键数据半同步;

5. 一致性:多活最难的是冲突,原则是"同一份数据只在一个单元写"。

追问

  • 问:异地双活读怎么保证一致?答:写后读在本单元,别跨单元读从库。
  • 问:跨城复制延迟多少?答:几十到上百毫秒级,不能当强一致用。

速查速记

| 场景 | 推荐方案 | 备选 |

|---|---|---|

| 关注关系 | 关系表双向索引 | 冗余粉丝表 |

| Feed 流 | 推拉结合 | 全推/全拉 |

| 资产扣减 | 原子更新+流水 | 乐观锁 |

| 排行榜 | Redis ZSET | DB 排序 |

| 日志埋点 | Kafka+数仓 | 不进 MySQL |

| 大字段 | 拆扩展表/OSS | 行内存 |

| count 大表 | 计数表/估算 | 走最小索引 |

| 软删唯一 | (列, deleted_at) | 拼接释放 |

常见坑与注意事项

  • 坑 1:把日志埋点写进 MySQL——写吞吐和分析场景完全不匹配,应走 Kafka + 数仓;
  • 坑 2:所有大表直接 DELETE 历史数据——大事务、慢、产生大量日志,优先分区 DROP;
  • 坑 3:大字段和主表混存——宽表回表代价高,长文本拆扩展表或存 OSS;
  • 坑 4:count(*) 大表当常规接口——要精确总数用计数表,别实时扫;
  • 坑 5:软删除 + 唯一索引不处理——删了的用户占着唯一键,新用户无法注册;
  • 注意:本篇均为公开题型与通用原理,不代表任何公司真实架构。

复习清单

| 题目 | 核心结论 | 易错点 |

|---|---|---|

| 关系表 | 双向索引/冗余 | 别只建单向 |

| Feed流 | 推拉结合 | 大V写放大 |

| 资产扣减 | 原子+流水 | 别先查后扣 |

| 聊天记录 | 会话分表归档 | 在线别扛历史 |

| 冷热分层 | 自动路由下沉 | 冷数据仍要可查 |

| 写后读 | 写后读走主 | 别全走从库 |

| 大字段 | 拆表/OSS | 别 SELECT * |

| count | 计数表/估算 | 别硬扫大表 |

| 软删唯一 | (列,deleted_at) | 别留死键冲突 |

练习题

练 1:设计一个关注关系表,要求能高效查"我关注谁"和"谁关注我",并防止重复关注。

<details>

<summary>参考答案</summary>

CREATE TABLE t_follow (
  user_id BIGINT, followee_id BIGINT, created_at DATETIME DEFAULT NOW(),
  PRIMARY KEY (user_id, followee_id),  -- 主键防重复关注
  KEY idx_followee(followee_id)        -- 反查粉丝
);

我关注谁按主键前缀 user_id 查;谁关注我按 idx_followee 查;主键唯一约束天然防重复。

</details>

练 2:聊天记录表每天新增千万行,在线库已千万级,给出存储与归档方案。

<details>

<summary>参考答案</summary>

按会话 ID 哈希分表打散;近期 3 个月消息热存在线库并挡 Redis;更早数据夜间批量迁到归档库/列存,按时间分区 DROP 在线老分区;归档库提供只读查询入口,删除前校验行数并备份。

</details>

下一篇预告:第 31 章 字节系MySQL真题精讲

— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 30 章 腾讯系MySQL真题精讲》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。