You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go Web应用中如何低内存导出MySQL数据至浏览器

Go Web 大数据量MySQL边查边导至浏览器(低内存方案)

核心思路

要解决全量加载数据导致的内存溢出问题,关键在于流式处理:

  • 对MySQL使用流式查询,让驱动分批从数据库拉取数据,而非一次性加载全量结果到内存
  • 直接将每批次读取的数据写入HTTP响应流,数据不经过中间内存缓存,边读边发

application/octet-stream只是用来触发浏览器下载行为的响应头类型,本质仍是流式传输CSV(或其他格式)数据,和CSV导出逻辑兼容。

具体实现步骤

1. 启用MySQL流式查询

Go的database/sql配合mysql驱动时,默认会将查询结果全量拉取到内存。需通过以下方式开启流式查询:

  • 执行db.Query后,调用rows.SetFetchSize(n)设置每次从数据库拉取的行数(n建议设为100~1000,平衡数据库交互次数和内存占用)
  • 确保数据库连接字符串开启interpolateParams=true(部分驱动版本需要此参数配合流式查询)

2. 设置HTTP响应头

配置响应头让浏览器触发下载,并声明流式传输:

w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", `attachment; filename="large_data.csv"`)
w.Header().Set("Transfer-Encoding", "chunked")
w.Header().Set("Cache-Control", "no-cache")

3. 逐行读取+流式写入

利用http.Flusher确保写入的数据即时发送到浏览器,避免服务器端缓冲区堆积:

  • 初始化CSV Writer直接绑定http.ResponseWriter
  • 先写入CSV表头,再循环读取数据库行,每读取一行就写入响应流,写完立即Flush

完整代码示例

package main

import (
	"encoding/csv"
	"net/http"
	"database/sql"
	"strconv"
	_ "github.com/go-sql-driver/mysql"
)

func exportLargeData(w http.ResponseWriter, r *http.Request) {
	// 初始化数据库连接(建议使用全局连接池)
	db, err := sql.Open("mysql", "username:password@tcp(127.0.0.1:3306)/your_db?interpolateParams=true")
	if err != nil {
		http.Error(w, "数据库连接失败", http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// 执行查询并开启流式拉取
	rows, err := db.Query("SELECT id, username, create_time FROM large_user_table")
	if err != nil {
		http.Error(w, "查询数据失败", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	// 设置流式查询批次大小(每次拉取100行)
	if err := rows.SetFetchSize(100); err != nil {
		http.Error(w, "设置流式查询参数失败", http.StatusInternalServerError)
		return
	}

	// 配置响应头
	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", `attachment; filename="user_export.csv"`)
	w.Header().Set("Transfer-Encoding", "chunked")
	w.Header().Set("Cache-Control", "no-cache")

	// 验证当前响应支持流式传输
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "当前服务器不支持流式响应", http.StatusInternalServerError)
		return
	}

	// 初始化CSV写入器,直接写入响应流
	csvWriter := csv.NewWriter(w)
	defer csvWriter.Flush()

	// 写入CSV表头
	if err := csvWriter.Write([]string{"ID", "用户名", "创建时间"}); err != nil {
		http.Error(w, "写入表头失败", http.StatusInternalServerError)
		return
	}
	csvWriter.Flush()
	flusher.Flush()

	// 逐行读取数据并写入响应
	var id int
	var username, createTime string
	for rows.Next() {
		if err := rows.Scan(&id, &username, &createTime); err != nil {
			http.Error(w, "读取行数据失败", http.StatusInternalServerError)
			return
		}

		// 生成CSV行
		record := []string{
			strconv.Itoa(id),
			username,
			createTime,
		}

		if err := csvWriter.Write(record); err != nil {
			http.Error(w, "写入行数据失败", http.StatusInternalServerError)
			return
		}

		// 即时刷新缓冲区,将数据发送到浏览器
		csvWriter.Flush()
		flusher.Flush()
	}

	// 检查遍历过程中的数据库错误
	if err := rows.Err(); err != nil {
		http.Error(w, "遍历数据出错", http.StatusInternalServerError)
		return
	}
}

func main() {
	http.HandleFunc("/export", exportLargeData)
	http.ListenAndServe(":8080", nil)
}

关键注意事项

  • 连接池配置:调整数据库连接池的maxOpenConns和maxIdleConns,避免长时间的流式查询占用过多连接
  • FetchSize调优:根据单条数据大小调整批次行数,避免过小导致数据库交互频繁,过大导致内存占用上升
  • 超时设置:在http.Server中设置合适的WriteTimeout,避免长耗时的导出被服务器提前断开连接
  • 资源清理:必须确保rows.Close()被执行,即使中途出错,避免数据库连接泄漏

内容的提问来源于stack exchange,提问作者elong musk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 14:10:24