来源:本人笔记「面试/面试经」「MySQL」「Redis」「1经验(慢SQL优化 / 乐观锁和悲观锁 / 高并发方案总结)」「1工作/合集(数据库日常运维 / 分布式Redis锁)」「SpringCloud学习(RabbitMQ / RabbitMQ部署指南)」。面试前一晚只看这一页。

一、数据库

1. 慢 SQL 优化(最高频,背五步)

  1. 检查是否走了索引,没走就改 SQL 用上索引
  2. 走了索引,再看是不是最优索引
  3. 查的字段是否都必须,有没有查出多余数据
  4. 数据量是否过多,是否该分库分表
  5. 机器性能配置是否太低,能否加资源

口诀补充:走不走索引,效率差几百倍。

2. SQL 写法优化清单

  • 批量操作
  • 连接的表不要过多
  • 避免 ​select *
  • 小表驱动大表
  • 使用 ​limit
  • 用 ​union all​ 代替 ​union
  • in 小,exists 大;in 先内后外,exists 先外后内

3. 引擎与索引(送分题)

  • 引擎:InnoDB、MyISAM、MEMORY
  • 索引:B+ Tree 索引、哈希索引、全文索引、普通索引

4. 锁(一句话说清)

  • 乐观锁:先改,提交时再看有没有人动过,有就重来
  • 悲观锁:先占住,别人等我改完才能动

5. 连接方式

连接 结果
内连接 inner join 两表都存在的行拼成一行;​from T1,T2 where ... 等价但效率更低,建议用 inner join
左外 left join 左表全保留,右表不匹配补 NULL
右外 right join 右表全保留,左表不匹配补 NULL
全联 full join 两边全保留,去重

6. 聚合函数

count / max / min / sum / avg,均以列为单位。

坑点:聚合函数默认忽略 NULL 记录;要让 NULL 参与计算需 ​ifnull(height,0)​。
​count(col)​ 只算非 NULL 行数,​count(*) 才算总行数。

7. 高并发三条(可结合项目讲)

  1. 频繁使用、并发率高的表用 ​DB.Update​ 更新对应字段,用 ​RF.Save 会存在字段被覆盖更新的情况
  2. 批处理与延迟提交,批量插入/更新,减少数据库往返次数
  3. 能用异步就用异步、消息队列处理,降低主线程阻塞

8. 运维速查 SQL(被问"你会哪些运维手段"时用)

查表已有索引(判断单列/组合)

  • Oracle:​user_indexes​ + ​user_ind_columns​,​LISTAGG 拼列名
  • SQL Server:​sys.indexes​ + ​sys.index_columns​ + ​sys.columns​,​FOR XML PATH 拼列名

抓慢 SQL(Oracle) :​dba_hist_snapshot​ 关联 ​dba_hist_sqlstat​,按 ​elapsed_time_delta​ 倒序取 TOP 10,再 ​select * from v$sql where sql_id='xxx' 看原语句。

其他常用:表空间使用情况、数据库连接数、最占空间的表、删除库锁/表锁、查看存储过程对应 SQL。

9. SQL 关键字速查

关键字 用途
GROUP BY 分组聚合
HAVING 过滤分组结果(聚合值不能用 WHERE 过滤)
ORDER BY 排序,默认 ASC
UNION 合并去重,​UNION ALL 保留重复
INTERSECT 交集
EXCEPT 差集
COLLATE 排序规则,​_CI_​ 不区分大小写 / ​_CS_ 区分大小写

10. 事务写法(C#,结合项目讲)

// 普通事务
using (var trans = DB.TransactionScope(CoreEntityDataProvider.ConnectionStringName))
{
    trans.Complete();
}
// 自治事务,不受嵌套事务影响
using (var trans = DB.AutonomousTransactionScope(CoreEntityDataProvider.ConnectionStringName))
{
    trans.Complete();
}

IN 参数上限坑(SQL Server) :IN 参数上限 1000,分批按 999 处理,多层循环分批查询后合并结果。

二、Redis

1. 缓存的意义与读写逻辑(必背)

缓存的意义在于可以保护后台的接口不被大量的请求所攻击。

  1. 客户端第一次请求:先从数据库查到数据,写入 Redis 缓存,后面都直接从 Redis 读
  2. 后台做了增、删、改:把 Redis 对应键值清除;前端再次请求,还是从数据库获取并重新写入缓存

一句话:读时回填,写时删除。

2. 常用命令

# 连接
redis-cli -h host -p port -a password
redis-cli -p 6379

# 备份与恢复
save                  # 在备份目录生成 dump.rdb
config get dir        # 查看备份路径
# 恢复:拷贝 dump.rdb 覆盖到目标机备份目录 -> 重启 redis -> 客户端刷新
类型 增/改 查 删
string set key value​、​mset k1 v1 k2 v2 get key​、​mget k1 k2 del key
hash hset key 键 值​、​hmset key 键1 值1 hget key 键​、​hgetall key hdel key​(整个)/ ​hdel key 键(单列)
list lpush​(左增)/ ​rpush(右增,可多值) lrange key 0 -1 lpop​ / ​rpop
set sadd key value smembers key —

3. 分布式 Redis 锁(项目实战,重点讲)

var key = $"JrSubmit:{submitData.ShippingOrderNo}";
using (RT.Service.Resolve<RedisLockAbstractHelper>()
    .LockWait(TimeSpan.FromMinutes(2), TimeSpan.FromMinutes(2),
              new List<string> { key }, $"当前自主维护单正在{isSubmitString},请稍后再试!"))
{
    // 业务逻辑
}

要点:key 按业务单据号粒度加锁(如 ​业务名:单据号​),多个 key 可批量加锁;​LockWait​ 两个 TimeSpan 分别是等待时长和持有时间;用 ​using 保证释放。

三、MQ(消息队列)

1. 为什么用 MQ:同步 vs 异步

同步通讯像打电话,实时响应但不能同时跟多个人通话;异步通讯像发邮件,可群发但响应有延迟。

同步调用的问题:耦合度高、性能和吞吐能力下降、有额外的资源消耗、有级联失败问题。

异步通讯(事件驱动,中间人 Broker)的好处:

  • 吞吐量提升:无需等待订阅者处理完成
  • 故障隔离:服务间没有直接调用,不存在级联失败
  • 调用间无阻塞,不造成无效资源占用
  • 耦合度极低,服务可灵活插拔
  • 流量削峰:流量波动多大都由 Broker 接收,订阅者按自己速度处理

缺点:架构变复杂、业务没有明显流程线不好管理;依赖 Broker 的可靠、安全、性能。

2. 技术选型对比

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala&Java
协议支持 AMQP、XMPP、SMTP、STOMP OpenWire、STOMP、REST、XMPP、AMQP 自定义协议 自定义协议
可用性 高 一般 高 高
单机吞吐量 一般 差 高 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒以内
消息可靠性 高 一般 高 一般

选型口诀:追求可用性选 Kafka / RocketMQ / RabbitMQ;追求可靠性选 RabbitMQ / RocketMQ;追求吞吐选 RocketMQ / Kafka;追求低延迟选 RabbitMQ / Kafka。

3. RabbitMQ 角色与消息模型

角色:publisher(生产者)、consumer(消费者)、exchange(交换机,负责路由)、queue(队列,存储消息)、virtualHost(虚拟主机,隔离不同租户)。

五个消息模型:

  1. 简单队列:生产者 → 队列 → 消费者
  2. WorkQueue:多个消费者绑定一个队列共同消费,一条消息只被一个消费者处理;默认平均分配,用 ​prefetch: 1​ 实现能者多劳
  3. Fanout:广播,消息交给所有绑定到交换机的队列
  4. Direct:定向,按 ​RoutingKey 精确匹配队列
  5. Topic:通配符,​#​ 匹配一个或多个词,​*​ 匹配恰好一个词(如 ​item.#​ 匹配 ​item.spu.insert​;​item.*​ 只匹配 ​item.spu)

关键结论:Exchange 只负责转发消息,不具备存储消息的能力;没有队列绑定或没有符合路由规则的队列,消息会丢失。

4. SpringAMQP 要点

三个功能:自动声明队列/交换机/绑定关系;基于注解的监听器模式异步接收;封装 ​RabbitTemplate 发送消息。

  • 发送:​rabbitTemplate.convertAndSend(exchangeName, routingKey, message)
  • 接收:​@RabbitListener(queues = "simple.queue")​,或 ​@RabbitListener(bindings = @QueueBinding(...)) 注解声明
  • 声明 Bean:​Queue​、​FanoutExchange​、​Binding
  • 消息转换器:默认 JDK 序列化(体积大、有安全漏洞、可读性差),改为 ​Jackson2JsonMessageConverter

5. 部署与集群

# 单机(Docker)
docker run -e RABBITMQ_DEFAULT_USER=itcast -e RABBITMQ_DEFAULT_PASS=123321 \
  --name mq --hostname mq1 -p 15672:15672 -p 5672:5672 -d rabbitmq:3-management

端口:5672 通信,15672 管理界面(初始 guest/guest)。

集群两种模式:

  • 普通模式:不做数据同步,每个 MQ 有自己的队列和数据(交换机等元数据会同步)。消息在 mq1 但连到 mq2,mq2 会去 mq1 拉取;mq1 宕机消息就丢失
  • 镜像模式:队列在各镜像节点之间同步,连任意节点都能取到消息,一个节点宕机不丢数据,代价是同步带宽消耗

集群前置:三台机器 ​/etc/hosts​ 互相写 ​mq1/mq2/mq3 并 ping 通。

四、临场串讲模板(1 分钟自我介绍里的技术线)

我做过 MES 制造系统开发,日常接触 Oracle / SQL Server / MySQL 三种库:慢 SQL 排查上我习惯先看执行计划是否走索引,再看索引是否最优、字段是否冗余,最后才考虑分库分表;数据库侧会写抓慢 SQL、查索引、查表空间和连接数的运维脚本。缓存用 Redis,读过回填、写时删除,分布式锁按单据号粒度加锁,防止重复提交和并发覆盖。服务间通信上,能异步的走 MQ,用 RabbitMQ 的 Topic 交换机做业务解耦和流量削峰,消费者侧用 prefetch 做能者多劳。