并发场景下FetchUserForAuthV1报错,FetchUserForAuth运行正常求助
并发场景下FetchUserForAuthV1过早超时故障的排查与优化建议
问题描述
我有两个功能相似的函数用于并发测试,其中FetchUserForAuthV1在请求量更低时就开始报超时错误,而FetchUserForAuth表现稳定。FetchUserForAuthV1通过结构体传递查询语句,并调用其FetchRows方法从数据库取数,目的是复用通用函数减少重复代码。
相关错误信息
context deadline exceeded
错误发生在common_db_operations.go的这段代码中:
tx, err := dbConnection.BeginTx(ctx, pgx.TxOptions{AccessMode: pgx.ReadWrite}) if err != nil { logger.Logger.Error("MODELS :: Error while begin transaction", zap.Error(err), zap.String("requestId", uuidString)) return id, err }
代码片段
FetchUserForAuth 函数
func FetchUserForAuth(email string) UserSchema { logger.Logger.Info("MODELS :: Will fetch user details for auth", zap.String("email", email)) var userData UserSchema dbConnection := DbPool() ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() // ctx := context.Background() tx, err := dbConnection.BeginTx(ctx, pgx.TxOptions{AccessMode: pgx.ReadOnly}) if err != nil { logger.Logger.Error("MODELS :: Error while begin transaction", zap.Error(err)) return userData } defer func() { if err != nil { tx.Rollback(ctx) } else { tx.Commit(ctx) } }() query := fmt.Sprintf(`SELECT u.id, u.first_name, u.last_name, u.email, u.password FROM users u WHERE u.email='%s' LIMIT 1`, email) logger.Logger.Info("MODELS :: Query", zap.String("query", query)) err = tx.QueryRow(ctx, query).Scan( &userData.Id, &userData.FirstName, &userData.LastName, &userData.Email, &userData.Password, ) if err != nil { if err == pgx.ErrNoRows { logger.Logger.Info("MODELS :: Query - No rows found. ", zap.String("query", query)) return userData } logger.Logger.Error("MODELS :: Error while executing query.", zap.Error(err), ) return userData } return userData }
FetchUserForAuthV1 函数
func FetchUserForAuthV1(email string) UserSchema { logger.Logger.Info("MODELS :: Will fetch user details for auth v1", zap.String("email", email)) var userData UserSchema query := fmt.Sprintf(`SELECT u.id, u.first_name, u.last_name, u.email, u.password FROM users u WHERE u.email='%s' LIMIT 1`, email) logger.Logger.Info("MODELS :: Query", zap.String("query", query)) queryToExecute := QueryStructToExecute{Query: query} rows, _, err := queryToExecute.FetchRows("") logger.Logger.Info("user models : ", zap.Any("rows ", rows), zap.Error(err)) if err != nil { return userData } if len(rows) == 0 { return userData } singleRow := rows[0] jsonbody, err := json.Marshal(singleRow) if err != nil { // do error check fmt.Println(err) } if err := json.Unmarshal(jsonbody, &userData); err != nil { // do error check fmt.Println(err) } logger.Logger.Info("MODELS :: before sending dat a", zap.Any("data", userData), zap.Any("id", userData.Id)) return userData }
排查思路与优化建议
- 修正事务模式:
FetchUserForAuth使用ReadOnly事务模式,而FetchRows用了ReadWrite模式。只读事务锁竞争更小,并发场景下不会抢占写锁资源,建议把FetchRows中的事务模式改为ReadOnly,降低数据库压力。 - 统一上下文超时:
FetchUserForAuth明确设置了60秒超时,检查FetchRows是否设置了合理的上下文超时,避免因默认短超时导致并发时快速触发超时,最好允许调用方传入自定义上下文。 - 优化连接池使用:确认
FetchRows是否正确复用数据库连接池,若每次调用未正确释放连接,并发时会耗尽连接池导致请求超时。确保和FetchUserForAuth一致,使用DbPool()获取连接并正确关闭事务/连接。 - 消除冗余数据转换:
FetchUserForAuthV1多了JSON序列化和反序列化步骤,并发下会增加CPU开销,间接导致请求变慢。建议在FetchRows中直接将结果映射到目标结构体,避免不必要的转换。 - 替换SQL拼接为参数化查询:两个函数都用
fmt.Sprintf拼接SQL,存在注入风险且无法复用数据库查询计划。改用参数化查询(如tx.QueryRow(ctx, "SELECT ... WHERE email=$1", email)),提升并发执行效率。 - 移除不必要的事务:单条SELECT语句无需使用事务,直接用
db.QueryRow代替事务操作,减少事务带来的额外开销和锁竞争。
内容的提问来源于stack exchange,提问作者Deepen Patel
相关产品推荐
相关产品推荐

