公司动态
Go-database-sql数据库操作实战从连接池到SQL注入防护
Go database-sql数据库操作实战从连接池到SQL注入防护文章导语database/sql是Go操作关系型数据库的标准接口。但它设计得很薄——不提供ORM功能需要开发者理解连接池、事务隔离级别、SQL注入防护等底层机制。本文从实战角度出发覆盖企业级数据库操作的核心实践。一、database/sql的架构设计// database/sql的接口隔离设计typeDBstruct{connector driver.Connector// 数据库驱动// 连接池管理freeConn[]*driverConn connRequestsmap[uint64]chanconnRequest// 配置maxOpenint// 最大连接数maxIdleint// 最大空闲连接数maxLifetime time.Duration// 连接最大存活时间}// 驱动的核心接口typeDriverinterface{Open(namestring)(Conn,error)}typeConninterface{Prepare(querystring)(Stmt,error)Close()errorBegin()(Tx,error)}Go通过这种设计实现了数据库操作层和驱动层的分离开发者使用统一的API操作不同的数据库。二、连接池的黄金配置funcinitDB()*sql.DB{db,err:sql.Open(postgres,hostlocalhost userapp dbnamemydb sslmodedisable)iferr!nil{log.Fatal(err)}// 连接池关键配置db.SetMaxOpenConns(25)// 最大打开连接数db.SetMaxIdleConns(10)// 最大空闲连接数db.SetConnMaxLifetime(5*time.Minute)// 连接最大存活时间db.SetConnMaxIdleTime(1*time.Minute)// 空闲连接超时// 验证连接iferr:db.Ping();err!nil{log.Fatal(err)}returndb}配置原则MaxOpenConns 数据库max_connections/ 实例数MaxIdleConns≈MaxOpenConns* 40%ConnMaxLifetime 数据库wait_timeoutMySQL默认8小时三、查询操作的最佳实践3.1 安全查询——永远不要拼接SQL// 危险——SQL注入query:fmt.Sprintf(SELECT * FROM users WHERE name %s,userName)// 安全——使用占位符row:db.QueryRow(SELECT id, name FROM users WHERE name $1,userName)// MySQL使用? PostgreSQL使用$1, $2...row:db.QueryRow(SELECT id, name FROM users WHERE name ?,userName)3.2 单行查询与多行查询// 单行查询varuser User err:db.QueryRow(SELECT id, name, email FROM users WHERE id $1,id).Scan(user.ID,user.Name,user.Email)iferrsql.ErrNoRows{// 未找到记录returnUser{},ErrNotFound}// 多行查询rows,err:db.Query(SELECT id, name FROM users WHERE status $1,active)iferr!nil{returnnil,err}deferrows.Close()// 必须关闭varusers[]Userforrows.Next(){varu Useriferr:rows.Scan(u.ID,u.Name);err!nil{returnnil,err}usersappend(users,u)}iferr:rows.Err();err!nil{// 检查遍历中的错误returnnil,err}3.3 批量操作// 使用预编译语句stmt,err:db.Prepare(INSERT INTO users(name, email) VALUES($1, $2))iferr!nil{returnerr}deferstmt.Close()for_,user:rangeusers{if_,err:stmt.Exec(user.Name,user.Email);err!nil{returnerr}}四、事务的正确使用// 事务的基本模式funcTransferMoney(fromID,toIDint,amountfloat64)error{tx,err:db.Begin()iferr!nil{returnerr}defertx.Rollback()// 确保异常时回滚// 扣款result,err:tx.Exec(UPDATE accounts SET balance balance - $1 WHERE id $2 AND balance $1,amount,fromID)iferr!nil{returnerr}ifrows,_:result.RowsAffected();rows0{returnerrors.New(余额不足)}// 入账if_,err:tx.Exec(UPDATE accounts SET balance balance $1 WHERE id $2,amount,toID);err!nil{returnerr}returntx.Commit()// 提交后defer的Rollback不会执行事务已结束}事务隔离级别// 设置事务隔离级别tx,err:db.BeginTx(ctx,sql.TxOptions{Isolation:sql.LevelSerializable,ReadOnly:false,})五、实战Repository模式的完整实现typeUserRepositorystruct{db*sql.DB}func(r*UserRepository)FindByID(ctx context.Context,idint)(*User,error){query:SELECT id, name, email, created_at FROM users WHERE id $1varuser User err:r.db.QueryRowContext(ctx,query,id).Scan(user.ID,user.Name,user.Email,user.CreatedAt,)iferrsql.ErrNoRows{returnnil,ErrNotFound}returnuser,err}func(r*UserRepository)FindByIDs(ctx context.Context,ids[]int)([]User,error){// 动态IN查询query,args,err:sqlx.In(SELECT id, name FROM users WHERE id IN (?),ids)iferr!nil{returnnil,err}queryr.db.Rebind(query)// 替换?为数据库对应的占位符rows,err:r.db.QueryContext(ctx,query,args...)// ...}六、全文总结永远用占位符杜绝SQL注入连接池合理配置Match资源不泄漏defer rows.Close()避免连接泄漏事务必须defer Rollback()异常时自动回滚使用QueryRowContext传递超时控制七、技术进阶展望sqlx/jmoiron的增强查询能力GORM的模型设计与查询优化数据库迁移工具(golang-migrate)参考文献Go database/sql包文档: https://pkg.go.dev/database/sqlGo database/sql tutorial: http://go-database-sql.orgPostgreSQL驱动: https://github.com/lib/pqMySQL驱动: https://github.com/go-sql-driver/mysqlOWASP - SQL Injection Prevention Cheat Sheet