fundamentals.md

·interview-questions

一条 SQL 查询语句是如何执行的? MySQL 的执行引擎有哪些?

Q:你在查询的时候调用的另一个服务宕机了怎么办?

A:嗯首先在调用失败之前应该就会有一些防御机制,比如超时控制和熔断降级等,同时也可以做有限次数的重试,对于一些可缓存的数据可以从比如说 redis 读取旧数据,当真的出现失败时应该会马上有实时监控的告警,比如钉钉,然后就可以先去看一下日志和告警信息来定位问题,再针对性地解决问题

Q:数据库的慢查询要怎么优化?

A:先查一下日志看看是哪些语句是慢查询,然后可以用 EXPLAIN 来分析一下执行计划,看看是否有索引没有命中、全表扫描还是排序开销大等问题,如果是索引没有命中就可以考虑加索引,如果是全表扫描就可以考虑优化 SQL 语句,或者是加一些缓存来减少数据库的压力;另外还可以考虑分库分表来提高性能;索引优化的话可以考虑使用覆盖索引来减少回表的次数,或者是用联合索引来减少索引的数量,也可以删除重复和无用的索引来让优化器更快选出最优索引;如果是排序开销大就可以考虑加一个临时表来存储中间结果,或者是用一些缓存来减少数据库的压力;另外也可以分多几个库,比如主库负责写入,从库负责读取,也可以分区分表来提高性能,或者重新设计表结构,非实时场景可以先写入消息队列然后再异步处理

Q:分库分表了解吗?项目中有没有实际用到?

A:有了解,分表就是把一张比较大的表按照某种规则拆分成多张同结构的小表,用来解决单表数据量过大导致的性能问题,比如说可以按时间、ID 或者是地理位置来分表;分库就是把一个数据库拆分成多个数据库,通常是把主库和从库分开,主库负责写入,从库负责读取,这样可以提高性能和可用性;在项目中我还没有实际用到分库分表,因为目前的项目数据量还不大,但我有考虑过在设计时就把这个架构考虑进去,以便后续扩展,比如说可以把用户表和游戏数据表分开,用户表可以按 ID 来分表,游戏数据表可以按时间来分表,这样就能更好地支持高并发和高可用性了;另外在设计时也要考虑到数据的一致性和完整性,比如说在分库分表后要保证跨库事务的一致性,可以使用两阶段提交或者是补偿事务等方式来实现

Q:如果现在有一个有十亿数据量的大表需要分表,该怎么做,分表之后怎么切业务

A:嗯首先要选一个既能均匀打散数据又能经常用来查询或关联的字段,比如 user_id 这种的做哈希分配,或者按时间做范围分区,设计好分表规则后就可以创建新的分表,然后把旧表的数据迁移到新表中,迁移时可以先把旧表的数据按规则分批次地插入到新表中,然后再把旧表的数据删除,最后再把旧表删除;在切换业务时可以先把新表的读写权限开放给部分用户进行灰度测试,测试通过后再全面切换到新表;在切换过程中要注意数据的一致性和完整性,比如说在切换前要先备份数据,在切换后要监控数据的一致性和完整性,确保没有数据丢失或错误

Q:讲一下常见的存储引擎

A:常见的存储引擎有 MyISAM、InnoDB 和 Memory 等,MyISAM 是 MySQL 早期版本的默认存储引擎,支持表级锁和全文索引,但不支持事务和外键约束,适合读多写少的场景;InnoDB 是 MySQL 的事务型存储引擎,也是现代 MySQL 版本的默认存储引擎,支持行级锁、事务和外键约束,适合高并发和复杂查询的场景;Memory 存储引擎是将数据存储在内存中,速度非常快,但数据不持久化,适合临时表和缓存等场景;另外还有一些其他的存储引擎,比如 CSV、ARCHIVE 和 FEDERATED 等,分别用于 CSV 文件存储、归档数据和分布式数据库等场景

Q:同样是一百万的数据量,读的话,InnoDB 和 MyISAM 哪个更快

A:如果只是纯粹的单次读的话,MyISAM 会稍微快一点,因为它没有事务和 MVCC 的元数据开销,但如果是高并发的读写混合场景下,InnoDB 的 Buffer Pool 同时缓存数据和索引,在预热后会更快,因为它支持行级锁和多版本并发控制(MVCC),可以更好地处理并发事务,避免了 MyISAM 的表级锁带来的性能瓶颈;另外 InnoDB 还支持更复杂的查询和事务处理,所以在大多数情况下,InnoDB 是更好的选择

Q:给你一个用户信息表,选择使用哪个存储引擎来存储,为什么

A:嗯用户信息表是属于读多写少的场景,所以我会优先选择默认的 InnoDB 引擎,它既能提供完整的事务和 MVCC,保证用户数据在并发修改时能原子且一致地提交,又支持外键约束和崩溃自动恢复,符合核心业务对数据可靠性的要求,只有在完全只读或者对高可用有特殊要求的极端场景下我才会考虑 MyISAM 或者其他引擎

Q:多级评论的表(最多二级),你会设计哪几个关键字段

A:我会让表里有 comment_id (主键)、post_id (归属资源)、user_id (评论人)、parent_id (父评论,NULL 表示一级)、root_id (根评论,用于快速聚合一级+二级)、reply_to_user_id (被@用户)、level (层级)、content (内容) 以及 created_at、updated_at 打点时间。 这样,当我要查询一个帖子下所有一级评论,就 WHERE post_id=? AND level=1;要加载某条一级评论的二级回复,就 WHERE root_id=? AND level=2。reply_to_user_id 让前端能正确显示“回复 @某人”。索引方面,我会针对 (post_id, level, created_at) 做分页,以及 (parent_id, root_id) 做回复聚合,保证高并发场景下也能快速返数据

Q:写 sql,查询二级评论最多的一级评论,取前十条

A:

-- 查询前 10 个二级评论最多的一级评论 ID 及其回复数
SELECT
root_id AS comment_id,
COUNT(*) AS reply_count
FROM comment
WHERE level = 2
GROUP BY root_id
ORDER BY reply_count DESC
LIMIT 10;

思路:先把所有二级评论(level=2)按 root_id 聚合,计算每个 root 的回复数,再按数量倒序取前 10

Q:MySQL 和 redis 的缓存一致性该如何保证

A:先更新 MySQL,写库之后再删除 redis 缓存,确保后续请求要么打到旧缓存要么打到库,避免出现反向失效,短延时后可以再删除一次缓存,防止并发写入时读到旧值,如果业务对一致性要求较高,可以在更新和删缓存之间加一下分布式锁,保证同一条记录在任何时刻只有一个线程在操作缓存;在写库时将缓存失效消息发送到消息队列,消费者异步订阅消息并在各节点删除或更新缓存,但要保证消息队列的消息不丢失,也可以通过监听 MySQL 的 binlog 来实时把数据变更同步到 redis

Q:数据量变成原来的十倍,该怎么办

A:主要有三个层面可以操作,一个是存储层,可以做分库分表和 MySQL 分区,把单表行数从千万级降到百万级以下,并重新评估索引,把高选择性字段建成联合或覆盖索引,也可以考虑只把热数据留在主库;缓存与异步层面的话可以考虑加深本地和 redis 的二级缓存,对于写入量大的统计、日志和推荐等可以用消息队列异步入库,减少对主库的直接冲击;最后应用与运维层面可以考虑读写分离,让从库承担更多读流量,关键服务拆分成独立的微服务做水平扩缩容,可以再结合告警和 k8s 或重新跑压测等

  • 有了解过 SQL 注入等安全问题吗?

  • MySQL 跟性能有关的参数你知道一些什么呢?比如说你自己在安装 MySQL 的时候会去做哪些配置,比如说它的默认配置你会去关注哪些参数,你会去改哪些吗?

  • MySQL 的缓存池是什么?使用 MySQL 时有遇到过什么性能方面的问题吗?

  • 我们再把这个场景具象一些,就是比如说现在有一个线上运行的服务,然后它有一个表结构,现在它要做这个表结构变更,可能是加索引,可能是加字段,那为了避免说这个表结构的变更动作对现有业务产生影响,或尽量减少影响,你觉得有什么样的这个准备工作或策略

  • 诶那个表结构怎么逐步变更啊,它是一张表,它可以部分数据变部分数据不变吗?业务要部分停机吗?

  • 如果是主从同步的场景,这种表结构变更也需要在多个实例中去重复做吗?

  • 主从同步的机制是什么样的?是哪些操作会同步哪些操作不会同步?

  • SQL 调优你了解吗,比如说你调试的一些 SQL 语句怎么验证它是否命中了一些索引,以及它的性能是否是最优,该通过什么样的方式去验证