Go定时任务执行SELECT *查询SQL Server数据不完整问题
核心问题分析
你的问题本质是拉取过程中因数据库恢复导致连接中断/结果集截断,但驱动未抛出明确错误,同时一次性拉取超大数据集也容易触发驱动或内存层面的隐性问题。COUNT(*)能返回正确行数,说明源表数据本身没问题,问题出在数据读取链路。
具体解决步骤
修复结果集读取的错误处理逻辑
很多人写Go数据库读取代码时,只判断Rows.Next()返回false就停止,忽略了循环中断可能是因为错误而非数据读完。必须在循环结束后检查rows.Err():rows, err := db.QueryContext(ctx, "SELECT * FROM your_table WITH (NOLOCK)") if err != nil { // 处理连接/查询错误 log.Fatal(err) } defer rows.Close() var dataSlice []YourStruct for rows.Next() { var item YourStruct if err := rows.Scan(&item.Field1, &item.Field2, ...); err != nil { // 处理单行扫描错误 log.Error(err) continue // 或中断重试 } dataSlice = append(dataSlice, item) } // 关键:检查循环是否因错误终止 if err := rows.Err(); err != nil { log.Error("读取结果集时出错:", err) // 触发重试逻辑 retryPull() }强制避开数据库恢复窗口
和DBA确认数据库自动恢复的固定时间段(比如每周日凌晨2-4点),直接调整cron定时任务的执行时间,完全避开这个窗口,从根源上消除927错误的触发条件。分批次拉取替代全量读取
一次性拉取337万行+400列的超大数据集,很容易触发驱动的隐性截断或内存溢出问题。改用按主键分页拉取,每次拉取1000-5000行:-- 假设表有自增主键id SELECT * FROM your_table WITH (NOLOCK) ORDER BY id OFFSET ? ROWS FETCH NEXT 1000 ROWS ONLY;在Go代码中循环执行这个查询,直到返回0行,累加所有批次的数据。这种方式不仅能避免单次结果集过大的问题,还能在某批次出错时只重试该批次,提升可靠性。
增强连接的重试机制
在SQL Server连接字符串中添加重试参数,遇到927这类临时连接错误时自动重试:server=your_server;database=your_db;user id=xxx;password=xxx;ConnectRetryCount=5;ConnectRetryInterval=10;ConnectRetryCount:连接失败后的重试次数ConnectRetryInterval:每次重试的间隔(秒)
校验拉取结果的完整性
拉取完成后,立即执行COUNT(*)(和查询用相同的隔离级别,比如WITH NOLOCK),对比拉取到的行数和COUNT结果:var totalRows int64 err := db.QueryRowContext(ctx, "SELECT COUNT(*) FROM your_table WITH (NOLOCK)").Scan(&totalRows) if err != nil { log.Error("获取总行数失败:", err) return } if int64(len(dataSlice)) != totalRows { log.Warnf("拉取不完整:实际拉取%d行,源表%d行", len(dataSlice), totalRows) // 触发重试 retryPull() }升级SQL Server驱动
确认你使用的go-mssqldb驱动是最新版本,旧版本存在一些结果集截断的已知bug,升级到最新版可以解决这类问题:go get github.com/microsoft/go-mssqldb@latest
内容的提问来源于stack exchange,提问作者mathias yeremia

