IO 性能优化
问题
Go 程序中 IO 是常见瓶颈,如何优化?
答案
IO 优化方向
| 方向 | 手段 |
|---|---|
| 减少 IO 次数 | 批量操作、缓存 |
| 减少 IO 数据量 | 压缩、精简查询字段 |
| 并行 IO | goroutine 并发、Pipeline |
| 连接复用 | 连接池(DB、HTTP、Redis) |
数据库 IO
// ❌ N+1 查询
for _, userID := range userIDs {
db.QueryRow("SELECT * FROM users WHERE id = ?", userID)
}
// ✅ 批量查询
db.Query("SELECT * FROM users WHERE id IN (?)", userIDs)
// ✅ 批量插入
tx := db.Begin()
for _, user := range users {
tx.Create(&user)
}
tx.Commit()
// ✅ GORM 批量插入
db.CreateInBatches(users, 100)
HTTP 客户端
// ✅ 复用 HTTP Client(内部维护连接池)
var httpClient = &http.Client{
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
Timeout: 10 * time.Second,
}
// ❌ 忘记关闭 Body → 连接无法复用
resp, _ := httpClient.Get(url)
// 必须读完并关闭 Body
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body) // 读空 Body
并发 IO
// 并发请求多个服务
func fetchAll(ctx context.Context, urls []string) ([]Result, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]Result, len(urls))
for i, url := range urls {
i, url := i, url
g.Go(func() error {
result, err := fetch(ctx, url)
if err != nil {
return err
}
results[i] = result
return nil
})
}
return results, g.Wait()
}
文件 IO
// ✅ 使用 bufio 缓冲读写
reader := bufio.NewReaderSize(file, 64*1024) // 64KB 缓冲
writer := bufio.NewWriterSize(file, 64*1024)
defer writer.Flush()
常见面试问题
Q1: HTTP 响应 Body 不关闭会怎样?
答案:连接不会归还到连接池,导致连接泄漏。达到 MaxIdleConnsPerHost 后新请求会创建新连接,最终 fd 耗尽。正确做法:
defer resp.Body.Close()
io.Copy(io.Discard, resp.Body) // 即使不需要内容也要读空
Q2: 如何减少数据库 IO?
答案:
- 缓存:热数据放 Redis
- 批量操作:批量查询/插入替代逐条
- 连接池:合理配置
MaxOpenConns - 索引:避免全表扫描
- 字段裁剪:只查需要的列,不要
SELECT *