跳到主要内容

IO 性能优化

问题

Go 程序中 IO 是常见瓶颈,如何优化?

答案

IO 优化方向

方向手段
减少 IO 次数批量操作、缓存
减少 IO 数据量压缩、精简查询字段
并行 IOgoroutine 并发、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?

答案

  1. 缓存:热数据放 Redis
  2. 批量操作:批量查询/插入替代逐条
  3. 连接池:合理配置 MaxOpenConns
  4. 索引:避免全表扫描
  5. 字段裁剪:只查需要的列,不要 SELECT *

相关链接