Go操作Oracle存储过程:3亿游标数据读取挂起且无数据
我基于database/sql结合github.com/godror/godror库,通过存储过程游标读取3亿条Oracle记录。程序处理9百万条数据时运行正常,但读取3亿条时,rows.Next()会挂起17分钟,结束后rbgMap长度为0。
生成sql.Rows的代码
func (o *OracleDb) OpenCursorTX(tx *sql.Tx, sqlStmt string) (*sql.Rows, error) { var cur driver.Rows ctx := context.Background() stmt, err := tx.PrepareContext(ctx, sqlStmt) if err != nil { logrus.Fatalf("error parsing cursor %s: %s", sqlStmt, err.Error()) return nil, err } if _, err := stmt.ExecContext(ctx, sql.Out{Dest: &cur}, godror.PrefetchCount(100000), godror.FetchArraySize(100000)); err != nil { logrus.Fatalf("error exec cursor %s: %s", sqlStmt, err.Error()) return nil, err } rows, err := godror.WrapRows(ctx, tx, cur) if err != nil { logrus.Fatalf("error cursor wrap rows %s: %s", sqlStmt, err.Error()) return nil, err } if err := stmt.Close(); err != nil { return nil, err } return rows, nil }
读取行的代码
rows, err := l.oracleDb.OpenCursorTX(tx, "begin drs_router_read.get_rate_b_groups(po_rate_b_groups => :po_rate_b_groups); end;") if err != nil { return err } var rbg domain.RmsGroupHist var gwgrId, direction int var dialCode, key, rbgDBegin, rbgDEnd string rbgMap := make(map[string][]domain.RmsGroupHist) i:=0 for rows.Next() { if err := rows.Scan(&gwgrId, &direction, &dialCode, &rbg.RmsgId, &rbgDBegin, &rbgDEnd); err != nil { return fmt.Errorf("error scan db rows loadRateBGroups %v", err) } rbg.DBegin = util.StrToInt64(rbgDBegin) rbg.DEnd = util.StrToInt64(rbgDEnd) key = domain.RBObjectKey + ":" + strconv.Itoa(gwgrId) + ":" + strconv.Itoa(direction) + ":" + dialCode rbgMap[key] = append(rbgMap[key], rbg) i++ if i%100000 == 0 { logrus.Infof("rows %d", i) } }
排查思路
验证存储过程游标输出
在Oracle客户端直接执行drs_router_read.get_rate_b_groups,确认游标是否能正常返回3亿条数据,排查存储过程内部是否存在大数据量下的逻辑异常(比如游标未正确打开、提前关闭,或执行超时)。调整Stmt.Close()时机
当前代码在WrapRows后立即关闭stmt,但sql.Rows可能依赖底层stmt资源。尝试将stmt.Close()延迟到rows处理完成后执行(比如用defer stmt.Close()),避免提前释放资源导致读取失败。优化预取参数
当前设置的PrefetchCount(100000)和FetchArraySize(100000)可能过大,导致单次预取占用过多数据库或内存资源,引发性能瓶颈。尝试降低参数值(比如10000),观察挂起情况是否缓解。添加上下文超时
当前使用无超时的context.Background(),大数据量读取时可能因数据库端执行时间过长触发隐式超时。尝试使用context.WithTimeout设置合理超时,并捕获超时错误,确认是否是超时导致的异常。监控内存与资源
3亿条数据存入rbgMap会占用巨量内存,可能导致GC频繁、程序假死甚至数据丢失。考虑分批次处理数据(比如每处理一定量后写入外部存储,或清理部分map),避免一次性加载全量数据到内存。检查Scan映射正确性
确认rows.Scan的参数顺序、数据类型与存储过程游标返回的字段完全匹配。大数据量下类型不匹配可能引发静默失败,导致无数据存入map。捕获循环后错误
在rows.Next()循环结束后,必须调用rows.Err()检查是否存在循环过程中未捕获的错误(比如网络中断、数据库连接异常),这类错误可能导致循环提前终止且无数据输出。
内容的提问来源于stack exchange,提问作者Vadim Voronin

