使用go-ora访问游标时遇到的错误
使用go-ora访问游标时遇到的错误 我正在使用 go-ora 来读取 Oracle 中的游标。 代码在单个请求下工作正常,但在施加负载时我遇到了问题。
错误已在 GitHub 上记录,请查看 issue 以获取更多信息。
我尝试执行一个存储过程,它在少量用户时工作,但当大量用户进入系统时会失败。
这是错误信息:
Jul 30 12:23:50 : 2024/07/30 12:23:50 http: panic serving 127.0.0.1:43974: runtime error: index out of range [0] with length 0
Jul 30 12:23:50 : goroutine 26 [running]:
Jul 30 12:23:50 : net/http.(*conn).serve.func1()
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/net/http/server.go:1868 +0xb9
Jul 30 12:23:50 : panic({0xa57c20?, 0xc000204840?})
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/runtime/panic.go:920 +0x270
Jul 30 12:23:50 : github.com/sijms/go-ora/v2.(*DataSet).setBitVector(0xc000232000?, {0xc000102900?, 0xc000081b70?, 0x434c9b?})
Jul 30 12:23:50 : C:/Users/kdf/go/pkg/mod/github.com/sijms/go-ora/v2@v2.8.19/data_set.go:94 +0xc5
Jul 30 12:23:50 : github.com/sijms/go-ora/v2.(*DataSet).load(0xc0000820a0, 0x1?)
Jul 30 12:23:50 : C:/Users/kdf/go/pkg/mod/github.com/sijms/go-ora/v2@v2.8.19/data_set.go:78 +0x1a9
Jul 30 12:23:50 : github.com/sijms/go-ora/v2.(*defaultStmt).read(0xc000152300, 0xc0000820a0)
Jul 30 12:23:50 : C:/Users/kdf/go/pkg/mod/github.com/sijms/go-ora/v2@v2.8.19/command.go:768 +0x17e
Jul 30 12:23:50 : github.com/sijms/go-ora/v2.(*Stmt)._exec(0xc000152300, {0xc0001a63c0, 0x8, 0x4eb5f0?})
Jul 30 12:23:50 : C:/Users/kdf/go/pkg/mod/github.com/sijms/go-ora/v2@v2.8.19/command.go:2001 +0xa28
Jul 30 12:23:50 : github.com/sijms/go-ora/v2.(*Stmt).ExecContext(0xc000152300, {0xb7c800, 0xc0001c01c0}, {0xc0001a63c0, 0x8, 0x8})
Jul 30 12:23:50 : C:/Users/kdf/go/pkg/mod/github.com/sijms/go-ora/v2@v2.8.19/command.go:1275 +0x1d9
Jul 30 12:23:50 : github.com/sijms/go-ora/v2.(*Connection).ExecContext(0xc0003bbab8?, {0xb7c800, 0xc0001c01c0}, {0xac5032?, 0x4e8660?}, {0xc0001a63c0, 0x8, 0x8})
Jul 30 12:23:50 : C:/Users/kdf/go/pkg/mod/github.com/sijms/go-ora/v2@v2.8.19/connection.go:1066 +0x85
Jul 30 12:23:50 : database/sql.ctxDriverExec({0xb7c800?, 0xc0001c01c0?}, {0x7f17b0495bf8?, 0xc00022c280?}, {0x0?, 0x0?}, {0xac5032?, 0xc0000826b0?}, {0xc0001a63c0, 0x8, ...})
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/ctxutil.go:31 +0xd7
Jul 30 12:23:50 : database/sql.(*DB).execDC.func2()
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/sql.go:1675 +0x165
Jul 30 12:23:50 : database/sql.withLock({0xb7a9c0, 0xc000098630}, 0xc000082870)
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/sql.go:3502 +0x82
Jul 30 12:23:50 : database/sql.(*DB).execDC(0x420a01?, {0xb7c800?, 0xc0001c01c0}, 0xc000098630, 0x3ed600?, {0xac5032, 0x8c}, {0xc0000836f8, 0x8, 0x8})
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/sql.go:1670 +0x24f
Jul 30 12:23:50 : database/sql.(*DB).exec(0x0?, {0xb7c800, 0xc0001c01c0}, {0xac5032, 0x8c}, {0xc0000836f8, 0x8, 0x8}, 0x8?)
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/sql.go:1655 +0xdb
Jul 30 12:23:50 : database/sql.(*DB).ExecContext.func1(0xcf?)
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/sql.go:1634 +0x4f
Jul 30 12:23:50 : database/sql.(*DB).retry(0x18?, 0xc000082a68)
Jul 30 12:23:50 : C:/sw/go1.21.6.windows-amd64/go/src/database/sql/sql.go:1538 +0x42
Jul 30 12:23:50 : database/sql.(*DB).ExecContext(0xa2bc80?, {0xb7c800?, 0xc0001c01c0?}, {0xac5032?, 0xaa9164?}, {0xc0000836f8?, 0xc0003ebee0?, 0xa?})
代码:
ctx := context.Background()
call := "BEGIN PROCEDURE(:1,:2); END;"
stmt, err := db.DB.PrepareContext(ctx, call)
if err != nil {
log.Errorf("Error preparing statement: %v", err)
return resp, err
}
defer stmt.Close()
var FavResult go_ora.RefCursor
_, err = stmt.ExecContext(ctx, CustId, sql.Out{Dest: &FavResult })
if err != nil {
log.Errorf("Error executing statement: %v", err)
return resp, err
}
rows, err := FavResult .Query()
if err != nil {
log.Errorf("Error fetching rows : %v", err)
return resp, err
}
defer rows.Close()
var resp []models.respDTO
for rows.Next_() {
var fav models.FavDTO
err := rows.Scan(
&fav.Latitude,
&fav.Longitude,
)
if err != nil {
log.Errorf("Error scanning row: %v", err)
}
resp = append(resp , fav)
}
感谢您的帮助。
3 回复
执行该查询时似乎没有数据。请在你的数据库客户端中检查以确保能找到数据。
是的,看起来是一个 goroutine 试图访问不存在的游标,所以抛出了这个错误。
我尝试添加互斥锁后,错误不再发生,但由于锁导致请求堆积,我遇到了超时问题。
这是一个已知的并发问题,发生在高负载下访问游标时。错误源于data_set.go:94的索引越界,当多个goroutine同时使用同一个连接或语句时会出现。
问题在于go-ora的RefCursor不是并发安全的。在高并发场景下,需要为每个goroutine创建独立的数据库连接或使用连接池的隔离策略。
以下是修复方案:
func getCursorData(ctx context.Context, db *sql.DB, CustId string) ([]models.respDTO, error) {
// 使用独立的连接而不是共享的连接池连接
conn, err := db.Conn(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get connection: %w", err)
}
defer conn.Close()
call := "BEGIN PROCEDURE(:1,:2); END;"
stmt, err := conn.PrepareContext(ctx, call)
if err != nil {
return nil, fmt.Errorf("error preparing statement: %w", err)
}
defer stmt.Close()
var FavResult go_ora.RefCursor
_, err = stmt.ExecContext(ctx, CustId, sql.Out{Dest: &FavResult})
if err != nil {
return nil, fmt.Errorf("error executing statement: %w", err)
}
rows, err := FavResult.Query()
if err != nil {
return nil, fmt.Errorf("error fetching rows: %w", err)
}
defer rows.Close()
var resp []models.respDTO
for rows.Next_() {
var fav models.FavDTO
err := rows.Scan(
&fav.Latitude,
&fav.Longitude,
)
if err != nil {
return nil, fmt.Errorf("error scanning row: %w", err)
}
resp = append(resp, fav)
}
return resp, nil
}
或者使用带最大连接数限制的连接池:
func main() {
// 配置连接池
db, err := sql.Open("oracle", "connection_string")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// 设置连接池参数
db.SetMaxOpenConns(100) // 根据负载调整
db.SetMaxIdleConns(20)
db.SetConnMaxLifetime(time.Hour)
// 在HTTP处理器中使用
http.HandleFunc("/endpoint", func(w http.ResponseWriter, r *http.Request) {
resp, err := getCursorData(r.Context(), db, "customer_id")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 处理响应
})
}
如果问题仍然存在,考虑在存储过程中添加SERIALIZABLE隔离级别或使用db.BeginTx开启事务:
func getCursorDataWithTx(ctx context.Context, db *sql.DB, CustId string) ([]models.respDTO, error) {
tx, err := db.BeginTx(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
})
if err != nil {
return nil, err
}
defer tx.Rollback()
call := "BEGIN PROCEDURE(:1,:2); END;"
stmt, err := tx.PrepareContext(ctx, call)
if err != nil {
return nil, err
}
defer stmt.Close()
var FavResult go_ora.RefCursor
_, err = stmt.ExecContext(ctx, CustId, sql.Out{Dest: &FavResult})
if err != nil {
return nil, err
}
rows, err := FavResult.Query()
if err != nil {
return nil, err
}
defer rows.Close()
var resp []models.respDTO
for rows.Next_() {
var fav models.FavDTO
err := rows.Scan(
&fav.Latitude,
&fav.Longitude,
)
if err != nil {
return nil, err
}
resp = append(resp, fav)
}
if err := tx.Commit(); err != nil {
return nil, err
}
return resp, nil
}
这些修改可以避免多个goroutine同时访问游标时的数据竞争问题。

