嵌套查询导致sqlexp.ReturnMessage获取RaisError消息阻塞异常
无法捕获SQL存储过程中特定RAISERROR消息的Go代码问题
我用Go代码调用SQL存储过程,尝试捕获存储过程中用RAISERROR抛出的消息,但只能获取前两条,第三条消息无法获取,程序会在retmsg.Message(ctx)处阻塞。
Go调用代码
retmsg := &sqlexp.ReturnMessage{} rows, err = db.QueryContext(ctx, query, retmsg) if err != nil { return } active := true for active { msg := retmsg.Message(ctx) switch m := msg.(type) { case sqlexp.MsgNotice: result = fmt.Sprintf("%s", m.Message) return case sqlexp.MsgNext: inresult := true for inresult { inresult = rows.Next() if inresult { var d interface{} if err = rows.Scan(&d); err == nil { result = fmt.Sprintf("%s %v", result, d) } } } case sqlexp.MsgNextResultSet: active = rows.NextResultSet() case sqlexp.MsgError: result = fmt.Sprintf("%s", m.Error) } }
SQL存储过程代码
declare @err_message nvarchar(255), @err_count int = 0 BEGIN if (isnull(@InStorageID,0)=0 and isnull(@OutStorageID,0)=0) begin SET @err_message = ' Message: empty storage ' select @err_count=@err_count+1 RAISERROR (@err_message,10, 1) end if (@Quantity<0 or @Quantity=0) begin SET @err_message = ' Message: items quantity less than zero ' select @err_count=@err_count+1 RAISERROR (@err_message,10, 1) end if not exists (select 1 from [dbo].[tblQuantity] qt where qt.ConsignmentID=@ConsignmentID and qt.StorageID=@OutStorageID and (qt.Quantity-@Quantity)>=0 ) and @OutStorageID>0 begin SET @err_message = ' Message: the quantity of goods is more than what is in stock ' select @err_count=@err_count+1 RAISERROR (@err_message,10, 1) end if (@err_count=0)...
问题细节
目前能获取到"Message: empty storage"和"Message: items quantity less than zero",但第三条消息无法获取,程序会卡在msg := retmsg.Message(ctx),进入sqlexp包的这段代码后阻塞:
func (m *ReturnMessage) Message(ctx context.Context) RawMessage { select { case <-ctx.Done(): return MsgNextResultSet{} case raw := <-m.queue: return raw } }
已经尝试修改上下文、给sqlexp代码添加default分支,均无效。怀疑是嵌套查询执行耗时过长,但不确定。
解决方案分析
1. 验证存储过程触发条件
第三条RAISERROR前的NOT EXISTS查询需要满足多个条件才会执行:@OutStorageID>0、@ConsignmentID对应记录存在、库存不足。先确认传入的参数是否完全符合触发逻辑,若参数不满足,这条RAISERROR根本不会执行,自然无消息抛出。
2. 修复Go代码的消息处理逻辑
现有代码捕获到第一条sqlexp.MsgNotice后直接return,导致后续消息被忽略:
case sqlexp.MsgNotice: result = fmt.Sprintf("%s", m.Message) return // 直接返回终止流程,后续消息无法处理
需改为追加消息并继续循环:
case sqlexp.MsgNotice: result += fmt.Sprintf("%s", m.Message) // 移除return,继续处理后续消息
3. 正确消费所有结果集
存储过程中每个RAISERROR后都有SELECT @err_count=@err_count+1,会产生独立结果集。若未正确消费所有结果集,驱动会阻塞等待处理。修改后的循环需加入错误检查:
active := true for active { msg := retmsg.Message(ctx) switch m := msg.(type) { case sqlexp.MsgNotice: result += fmt.Sprintf("%s", m.Message) case sqlexp.MsgNext: inresult := true for inresult { inresult = rows.Next() if inresult { var d interface{} if err = rows.Scan(&d); err != nil { result += fmt.Sprintf("Scan error: %v", err) break } result = fmt.Sprintf("%s %v", result, d) } } // 检查结果集遍历后的错误 if err = rows.Err(); err != nil { result += fmt.Sprintf("Result set error: %v", err) break } case sqlexp.MsgNextResultSet: active = rows.NextResultSet() // 切换结果集后检查错误 if err = rows.Err(); err != nil { result += fmt.Sprintf("ResultSet switch error: %v", err) active = false } case sqlexp.MsgError: result += fmt.Sprintf("%s", m.Error) } }
4. 设置上下文超时
若嵌套查询确实耗时过长,给ctx设置合理超时避免无限阻塞:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() rows, err = db.QueryContext(ctx, query, retmsg)
内容的提问来源于stack exchange,提问作者Spison
相关产品推荐
相关产品推荐

