前几天分享过一篇训练营的朋友在阿里的一面面经,挺简单的她也是很轻松的过了,感兴趣的可以看一下我之前发的文章。
今天要分享的还是她的面经,**美团的一面**,感觉比阿里的**难一些**,各位观众老爷你怎么看?
![image.png](https://static.golangjob.cn/241206/2a22f642d7bc63022f1e9b3c5ee9fd73.png)
1. 自我介绍
### 2. 如何治理大表?
治理大表通常涉及到以下几个方面:
- **分库分表**:根据业务逻辑或数据分布将数据分散到多个数据库或表中。
- **分区**:对于单个大表,可以采用分区技术(如范围、列表、哈希分区)来提高查询性能。
- **归档**:将不再频繁访问的历史数据迁移到低成本存储介质。
- **优化查询**:确保所有查询语句都是高效的,并且只选择需要的列。
- **索引管理**:为经常用于查询条件的字段创建索引,但避免过度索引以防止写入性能下降。
### 3. 对于千万级的mysql表,如何能够使他读取的更快?
要加快大型MySQL表的读取速度,可以采取以下措施:
- **索引优化**:为常用的查询条件建立有效的索引,包括联合索引。
- **覆盖索引**:当查询的所有字段都在索引中时,直接从索引树获取结果,而不必回表查找。
- **分区表**:按照时间戳或其他标准对表进行分区,减少扫描的数据量。
- **缓存机制**:利用Redis等内存型数据库作为缓存层,减轻数据库压力。
- **读写分离**:设置主从架构,读操作分发到从节点执行。
- **调整配置参数**:比如增大innodb_buffer_pool_size,提高缓冲区命中率。
### 4. 索引应该怎么建?
索引的设计应遵循以下原则:
- **选择性高的字段优先**:选择性高意味着该字段能区分更多的记录,这样的索引更加有效。
- **考虑查询模式**:分析应用中最常见的查询模式,确保这些查询能够充分利用索引。
- **联合索引**:如果多个字段总是同时出现在WHERE子句中,可以考虑创建联合索引。
- **避免过多索引**:每增加一个索引都会影响插入、更新和删除操作的速度。
- **定期审查**:随着数据的增长和查询模式的变化,原有的索引可能不再适用,需定期评估并调整。
### 5. 考察联合索引的最左匹配原则?
联合索引是由多个字段组成的复合索引,它的工作原理是基于最左前缀原则。这意味着查询条件必须包含索引的第一个字段才能有效地使用这个索引。例如,如果有联合索引(a, b),那么查询条件中至少要包含a才能利用该索引;如果只有b,则无法利用此索引。这是因为B+树结构决定了索引的遍历方式是从左至右逐层深入。
### 6. 说一下一级索引和二级索引?
在关系型数据库中:
- **一级索引**(也称聚集索引)通常是主键索引,它定义了行在磁盘上的物理存储顺序。
- **二级索引**(非聚集索引)是指除了主键之外的其他索引,它们不改变行的物理排序,而是指向主键值来间接定位行位置。
### 7. 优化sql的方案?
SQL优化可以从多个角度入手:
- **重写查询**:简化复杂查询,分解成简单的子查询或者使用JOIN代替子查询。
- **限制返回的数据量**:只选取必要的列,而不是使用`SELECT *`。
- **适当使用索引**:确保查询条件能够命中索引,从而减少全表扫描。
- **使用EXPLAIN分析**:通过`EXPLAIN`命令查看查询执行计划,找出潜在的性能瓶颈。
- **批量处理**:尽量使用批量插入、更新,减少网络开销。
### 8. 分页的优化?
传统的分页方法依赖于`LIMIT`和`OFFSET`,但这会导致性能问题,特别是在大数据集上。优化分页的方法包括:
- **基于主键或唯一键分页**:每次查询指定上次返回的最大ID,然后在此基础上加上新的偏移量。
- **游标分页**:使用特定字段作为游标,每次请求携带上一次响应中的最后一个游标值,以此来确定下一页的数据起点。
### 9. 不同隔离级别的区别?
事务隔离级别决定了并发事务之间可见性和相互影响的程度,主要有四种:
- **读未提交**(Read Uncommitted):最低隔离级别,允许脏读、不可重复读和幻读。
- **读已提交**(Read Committed):禁止脏读,但仍可能发生不可重复读和幻读。
- **可重复读**(Repeatable Read):保证同一事务内多次读取相同数据的一致性,但可能存在幻读。
- **序列化**(Serializable):最高隔离级别,完全杜绝了脏读、不可重复读和幻读的问题。
### 10. 如何在实践中构造一个不可重复读的现象?
要在较低的隔离级别(如读已提交)下构造不可重复读现象,可以通过以下步骤:
- 开启两个事务T1和T2。
- T1执行查询并读取某条记录。
- 在T1尚未提交之前,T2修改这条记录并提交。
- T1再次查询同一记录,发现内容发生变化,这就是不可重复读。
### 11. Redis的使用场景?
Redis适用于多种场景:
- **缓存**:快速访问热点数据,减轻数据库负担。
- **消息队列**:发布/订阅模型适合实时通知和异步任务处理。
- **会话存储**:替代传统数据库保存用户会话信息。
- **排行榜**:利用有序集合实现排名统计。
- **计数器**:原子递增/递减操作非常适合计数用途。
### 12. 为什么直接更新缓存会有一致性问题?
直接更新缓存而不同步更新数据库可能导致数据不一致,因为缓存和持久层之间的状态不同步。例如,当缓存失效期间或缓存被清除后重新加载数据时,可能会出现缓存中的数据与数据库中的最新数据不符的情况。
### 13. 删除缓存也会有一致性问题,什么场景下使用删除缓存会产生一致性问题?
删除缓存时的一致性问题通常发生在多实例环境下,其中不同的服务器可能持有不同的缓存副本。具体场景如下:
- **缓存击穿**:当大量并发请求同时到达,导致缓存全部失效,所有请求直接打到数据库,造成数据库压力骤增。
- **缓存雪崩**:某个时间段内大量缓存同时过期,造成短时间内大量请求涌向数据库。
- **缓存穿透**:恶意请求故意查询不存在的数据,导致每次查询都去数据库查找,浪费资源。
### 14. 延迟双删的具体实现是什么样的?
延迟双删是一种解决缓存击穿的策略,其实现方式是:
- 当缓存项到期时,不是立即删除,而是将其标记为“正在刷新”状态。
- 设置一个短暂的时间窗口,在这段时间内任何尝试访问该缓存项的请求都将被阻塞,直到新值生成完毕。
- 如果在这段时间内没有成功刷新缓存,则真正删除该项,否则更新其内容。
### 15. 当你设计一个系统会考虑哪些?
设计系统时应考虑的因素包括但不限于:
- **功能性需求**:确保满足业务要求。
- **非功能性需求**:如性能、安全性、可用性、扩展性等。
- **数据一致性**:尤其是在分布式环境中,确保数据同步正确。
- **容错能力**:系统应当具备一定的故障恢复机制。
- **用户体验**:界面友好,易于使用。
- **成本效益**:控制硬件资源消耗,降低运营成本。
### 16. 监控告警一般监控哪些东西?
监控系统通常会关注以下几个方面:
- **系统资源利用率**:CPU、内存、磁盘I/O、网络带宽等。
- **应用程序健康状况**:响应时间、错误率、吞吐量等。
- **日志文件**:捕捉异常信息,帮助排查问题。
- **自定义指标**:根据业务特性设定关键绩效指标(KPI),如订单处理时间、交易成功率等。
### 编程题
#### 编程题 1:合并两个有序链表
```go
package main
type ListNode struct {
Val int
Next *ListNode
}
func mergeTwoLists(l1, l2 *ListNode) *ListNode {
dummy := &ListNode{}
current := dummy
for l1 != nil && l2 != nil {
if l1.Val < l2.Val {
current.Next = l1
l1 = l1.Next
} else {
current.Next = l2
l2 = l2.Next
}
current = current.Next
}
if l1 != nil {
current.Next = l1
} else {
current.Next = l2
}
return dummy.Next
}
```
#### 编程题 2:合并多个有序链表
这个问题可以通过优先队列(最小堆)来高效解决:
```go
package main
import "container/heap"
type ListNode struct {
Val int
Next *ListNode
}
// MinHeap 实现了一个最小堆
type MinHeap []*ListNode
func (h MinHeap) Len() int { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i].Val < h[j].Val }
func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *MinHeap) Push(x interface{}) { *h = append(*h, x.(*ListNode)) }
func (h *MinHeap) Pop() interface{} {
old := *h
n := len(old)
item := old[n-1]
*h = old[0 : n-1]
return item
}
func mergeKLists(lists []*ListNode) *ListNode {
minHeap := &MinHeap{}
heap.Init(minHeap)
// 将每个链表的第一个元素加入最小堆
for _, l := range lists {
if l != nil {
heap.Push(minHeap, l)
}
}
dummy := &ListNode{}
tail := dummy
for minHeap.Len() > 0 {
smallest := heap.Pop(minHeap).(*ListNode)
tail.Next = smallest
tail = tail.Next
if smallest.Next != nil {
heap.Push(minHeap, smallest.Next)
}
}
return dummy.Next
}
```
## 欢迎关注 ❤
我们搞了一个**免费的面试真题共享群**,互通有无,一起刷题进步。
**没准能让你能刷到自己意向公司的最新面试题呢。**
感兴趣的朋友们可以加我微信:**wangzhongyang1993**,备注:面试群。
有疑问加站长微信联系(非本文作者))