Golang操作MySQL用IN条件删除仅删一条数据的问题排查
问题
使用Go 1.16结合go-sql-driver/mysql v1.7.1驱动执行MySQL的DELETE语句,目标通过IN条件批量删除记录,但实际仅删除了一条数据。相关代码如下:
// Go版本声明 go 1.16
import ( "database/sql" "errors" "fmt" "io" "log" "net/http" "strconv" "strings" "time" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" _ "github.com/go-sql-driver/mysql" //v1.7.1 )
func SelectQuery(action string, schema string) string { if action == "del" { switch schema { case "schema1": return "DELETE FROM `schema1` WHERE `id` IN (?)" default: panic("bad request") } } return "" // 补充原代码缺失的返回值 }
func DelAction(db *sql.DB, schema string, list []int) int64 { strList := []string{} for _, v := range list { // ID列表示例:[1, 2, 3] strList = append(strList, strconv.Itoa(v)) } delQuery := SelectQuery("del", schema) // 得到SQL:"DELETE FROM `schema1` WHERE `id` IN (?)" param := strings.Join(strList, ",") // param值为:"1,2,3" delRes, err := db.Exec(delQuery, param) delCnt, err := delRes.RowsAffected() return delCnt // 实际仅删除1条记录 }
补充:待删除ID通过查询得到,代码示例:
res, err := db.Query(query) // 查询ID的SQL语句 list := []int{} for res.Next() { u := &Logs{} // Logs结构体包含int类型的id字段 if err := res.Scan(&u.id); err != nil { fmt.Println("scan error") panic(err.Error()) } else { list = append(list, u.id) } }
原因分析
你把多个ID拼接成单个字符串"1,2,3"作为参数传入SQL,但go-sql-driver/mysql会将这个字符串当作单个完整值处理。实际执行的SQL等价于:
DELETE FROM `schema1` WHERE `id` IN ('1,2,3')
MySQL会把字符串'1,2,3'隐式转换为整数1,因此只会匹配ID为1的记录,最终只删除一条数据。
解决方案
方法一:动态生成占位符(推荐,安全且性能优)
根据ID列表的长度,动态生成对应数量的?占位符,将每个ID作为独立参数传入,既能避免SQL注入,又能正常利用索引:
func DelAction(db *sql.DB, schema string, list []int) int64 { if len(list) == 0 { return 0 // 空列表直接返回,避免生成无效SQL } // 生成对应数量的占位符,比如3个ID生成"?, ?, ?" placeholders := strings.Repeat("?, ", len(list)) placeholders = strings.TrimSuffix(placeholders, ", ") // 替换原SQL中的单个?为动态占位符 delQuery := strings.Replace(SelectQuery("del", schema), "?", placeholders, 1) // 将int切片转为interface{}切片,作为Exec的参数 args := make([]interface{}, len(list)) for i, v := range list { args[i] = v } delRes, err := db.Exec(delQuery, args...) if err != nil { log.Fatal(err) // 根据业务需求调整错误处理逻辑 } delCnt, err := delRes.RowsAffected() if err != nil { log.Fatal(err) } return delCnt }
方法二:使用FIND_IN_SET函数(不推荐,仅作备选)
如果不想修改占位符,可以用MySQL的FIND_IN_SET函数,但这种方式无法利用ID索引,数据量大时性能极差,且若ID来源不可信存在SQL注入风险:
func DelAction(db *sql.DB, schema string, list []int) int64 { if len(list) == 0 { return 0 } strList := []string{} for _, v := range list { strList = append(strList, strconv.Itoa(v)) } param := strings.Join(strList, ",") // 修改SQL使用FIND_IN_SET delQuery := "DELETE FROM `schema1` WHERE FIND_IN_SET(`id`, ?)" delRes, err := db.Exec(delQuery, param) if err != nil { log.Fatal(err) } delCnt, err := delRes.RowsAffected() if err != nil { log.Fatal(err) } return delCnt }
内容的提问来源于stack exchange,提问作者xbirdx
相关产品推荐
相关产品推荐

