Golang CRUD 操作与预处理语句

发布时间:2026/10/3 12:54:19

Golang CRUD 操作与预处理语句 CRUD 操作与预处理语句核心概念CRUDCreate/Read/Update/Delete是数据库操作的基本功。在database/sql体系下除了直接拼 SQL 字符串还有**预处理语句Prepared Statement**这一利器。预处理将 SQL 模板和参数分离带来三个好处安全参数自动转义从根本上杜绝 SQL 注入性能同一条 SQL 只解析一次多次执行复用执行计划类型安全驱动负责 Go 类型到数据库类型的转换预处理 vs 直接执行// 直接执行每次都要解析 SQLdb.Exec(INSERT INTO users(name, age) VALUES(张三, 25))// 预处理SQL 只解析一次后续执行只传参数stmt,_:db.Prepare(INSERT INTO users(name, age) VALUES(?, ?))deferstmt.Close()stmt.Exec(张三,25)stmt.Exec(李四,32)// 复用同一个执行计划NULL 值处理数据库中的 NULL 是一个独特的值它不等于空字符串也不等于 0。Go 的基础类型string, int, float64无法表达 NULL直接Scan会报错。方案一sql.NullXxx 类型varname sql.NullStringvarage sql.NullInt64 err:db.QueryRow(SELECT name, age FROM users WHERE id ?,1).Scan(name,age)ifname.Valid{fmt.Println(name.String)// 非 NULL 时可以取值}else{fmt.Println(name is NULL)}sql.NullString是一个结构体type NullString struct { String string; Valid bool }。Valid为 false 表示数据库值为 NULL。方案二使用指针类型varname*stringvarage*intdb.QueryRow(SELECT name, age FROM users WHERE id ?,1).Scan(name,age)ifname!nil{fmt.Println(*name)// 解引用}else{fmt.Println(NULL)}指针方案更简洁但每次都要解引用且 nil 指针解引用会 panic。方案三COALESCE 在 SQL 层处理SELECTCOALESCE(name,),COALESCE(age,0)FROMusersWHEREid1直接在 SQL 里把 NULL 转成默认值Go 侧用基础类型接收。简单但丢失了原始值是否为 NULL的信息。完整练习代码// crud_prepared_statements.gopackagemainimport(database/sqlfmtlog_modernc.org/sqlite)// User 业务结构体typeUserstruct{IDint64NamestringEmailstringAgeintBiostring// 可能为 NULL}funcmain(){db,err:sql.Open(sqlite,:memory:)iferr!nil{log.Fatal(err)}deferdb.Close()// 建表bio 字段允许 NULLdb.Exec(CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, age INTEGER DEFAULT 0, bio TEXT ))// // 1. Create: 预处理批量插入// fmt.Println( 批量插入预处理语句 )insertStmt,err:db.Prepare(INSERT INTO users(name, email, age, bio) VALUES(?, ?, ?, ?))iferr!nil{log.Fatal(err)}deferinsertStmt.Close()users:[]User{{Name:张三,Email:zhangsantest.com,Age:28,Bio:后端工程师},{Name:李四,Email:lisitest.com,Age:32,Bio:},// Bio 为空字符串{Name:王五,Email:wangwutest.com,Age:25,Bio:},// Bio 为 NULL}fori,u:rangeusers{varbioArginterface{}ifu.Bioi2{bioArgnil// 王五的 bio 存为 NULL}else{bioArgu.Bio}res,err:insertStmt.Exec(u.Name,u.Email,u.Age,bioArg)iferr!nil{log.Printf(插入 %s 失败: %v,u.Name,err)continue}id,_:res.LastInsertId()fmt.Printf( 插入成功: ID%d, %s\n,id,u.Name)}// // 2. Read: 全量查询 NULL 处理// fmt.Println(\n 查询所有用户NULL 处理 )allUsers,err:queryAllUsers(db)iferr!nil{log.Fatal(err)}for_,u:rangeallUsers{fmt.Printf( [%d] %s %s %d岁 | bio: %s\n,u.ID,u.Name,u.Email,u.Age,u.Bio)}// // 3. Read: 按 ID 查询单个用户// fmt.Println(\n 按 ID 查询 )u,err:getUserByID(db,1)iferr!nil{log.Fatal(err)}fmt.Printf( 用户: %v\n,u)// 查询不存在_,errgetUserByID(db,999)iferrsql.ErrNoRows{fmt.Println( ID999 的用户不存在预期行为)}// // 4. Update: 更新用户信息// fmt.Println(\n 更新操作 )affected,err:updateUserAge(db,1,29)iferr!nil{log.Fatal(err)}fmt.Printf( 更新影响行数: %d\n,affected)u,_getUserByID(db,1)fmt.Printf( 更新后: %s, %d岁\n,u.Name,u.Age)// // 5. Delete: 删除用户// fmt.Println(\n 删除操作 )deleted,err:deleteUser(db,3)iferr!nil{log.Fatal(err)}fmt.Printf( 删除影响行数: %d\n,deleted)// 验证删除结果count:countUsers(db)fmt.Printf( 剩余用户数: %d\n,count)// // 6. 高级查询条件筛选 排序 分页// fmt.Println(\n 分页查询: age 26, 第1页 )pageUsers,err:queryUsersWithFilter(db,26,10,0)iferr!nil{log.Fatal(err)}for_,u:rangepageUsers{fmt.Printf( [%d] %s, %d岁\n,u.ID,u.Name,u.Age)}}// queryAllUsers 查询所有用户正确处理 NULL 值funcqueryAllUsers(db*sql.DB)([]User,error){rows,err:db.Query(SELECT id, name, email, age, bio FROM users ORDER BY id)iferr!nil{returnnil,err}deferrows.Close()varusers[]Userforrows.Next(){varu Uservarbio sql.NullString// 用 sql.NullString 接收可能为 NULL 的字段iferr:rows.Scan(u.ID,u.Name,u.Email,u.Age,bio);err!nil{returnnil,err}ifbio.Valid{u.Biobio.String}else{u.Bio(未填写)}usersappend(users,u)}returnusers,rows.Err()}// getUserByID 按 ID 查询单个用户funcgetUserByID(db*sql.DB,idint64)(User,error){varu Uservarbio sql.NullString err:db.QueryRow(SELECT id, name, email, age, bio FROM users WHERE id ?,id,).Scan(u.ID,u.Name,u.Email,u.Age,bio)ifbio.Valid{u.Biobio.String}returnu,err}// updateUserAge 更新用户年龄funcupdateUserAge(db*sql.DB,idint64,ageint)(int64,error){res,err:db.Exec(UPDATE users SET age ? WHERE id ?,age,id)iferr!nil{return0,err}returnres.RowsAffected()}// deleteUser 删除用户funcdeleteUser(db*sql.DB,idint64)(int64,error){res,err:db.Exec(DELETE FROM users WHERE id ?,id)iferr!nil{return0,err}returnres.RowsAffected()}// countUsers 统计用户总数funccountUsers(db*sql.DB)int{varcountintdb.QueryRow(SELECT COUNT(*) FROM users).Scan(count)returncount}// queryUsersWithFilter 条件筛选 分页查询funcqueryUsersWithFilter(db*sql.DB,minAge,limit,offsetint)([]User,error){rows,err:db.Query(SELECT id, name, email, age, bio FROM users WHERE age ? ORDER BY age LIMIT ? OFFSET ?,minAge,limit,offset,)iferr!nil{returnnil,err}deferrows.Close()varusers[]Userforrows.Next(){varu Uservarbio sql.NullStringiferr:rows.Scan(u.ID,u.Name,u.Email,u.Age,bio);err!nil{returnnil,err}ifbio.Valid{u.Biobio.String}usersappend(users,u)}returnusers,rows.Err()}运行方式mkdir-pdemo2cddemo2 go mod init demo2 go get modernc.org/sqlite go run main.go要点总结预处理语句db.Prepare()返回*sql.Stmt适合同一条 SQL 反复执行的场景用完必须stmt.Close()NULL 处理三方案sql.NullString最规范有 Valid 标志指针方案最简洁但有 panic 风险COALESCE最简单但丢失 NULL 语义参数占位符?是database/sql的标准写法驱动负责转义和类型转换永远不要手动拼接 SQL 字符串Scan 目标参数顺序必须与 SELECT 字段顺序一一对应数量不匹配会报错rows.Err()必须在循环结束后检查——Next()返回 false 可能是正常结束也可能是出错只有Err()能区分
延伸阅读

更多相关文章

2026/9/28 5:01:13

Module Builder——Gem300之E40 ProcessJob模块

E40 ProcessJob(工艺作业,简称PJ)是SEMI E40(Processing Management)标准定义的核心对象,专门用于规范设备对物料(晶圆/基板)执行具体工艺加工的全生命周期管理。它是GEM300体系中真…

2026/10/3 12:50:31

基于YOLOv8深度学习的道路裂缝检测系统

基于YOLOv8深度学习的道路裂缝检测系统 一、项目背景 道路裂缝是公路病害中最常见的形式之一,及时发现和修复裂缝对于保障道路安全、延长道路使用寿命具有重要意义。传统的人工巡检方式效率低、成本高,而基于深度学习的目标检测方法为道路裂缝的自动化检…

2026/10/3 12:45:30

Smartstore代码结构解读:6大核心项目30分钟看懂源码组织

Smartstore代码结构解读:6大核心项目30分钟看懂源码组织 【免费下载链接】Smartstore A modular, scalable and ultra-fast open-source all-in-one eCommerce platform built on ASP.NET Core 10 项目地址: https://gitcode.com/GitHub_Trending/smar/Smartstore…

2026/10/2 8:16:46

东莞市品牌网站建设报价常见报错与解决

东莞品牌网站建设报价单背后:一份保姆级建站教程避坑实录 网站做好了没人访问,这大概是很多老板最头疼的事。花了大几万做的品牌站,上线后流量惨淡,比路边摊还冷清。别急着骂外包公司,很多“东莞品牌网站建设报价”里藏着不少猫腻,比如用模板站冒充定制…

2026/10/2 18:20:53

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/10/1 10:48:55

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/10/3 0:04:31

国内大学生必备的AI写作辅助软件是哪款?

国内高校学生在论文写作过程中,越来越依赖AI辅助工具提升效率,主流方案以本土化全流程工具为核心,结合通用大模型与专业插件,覆盖选题构思、框架搭建、初稿撰写、查重降重、格式调整等关键环节,本文将深入解析当前主流…

2026/10/3 0:04:31

Codex接入Jev模型完整指南:配置方法、本地部署与踩坑排查

最近不少人在讨论 Codex 搭配 Jev 这套玩法,我一开始没太当回事,直到自己把 Jev 接进 Codex跑了几轮编码任务之后,才明白那些说“直接起飞”的人是怎么想的。Codex 作为工具本身已经够能打了,但模型固定、上下文策略固定&#xff…

2026/10/3 0:04:31

GitHub 热门: NVIDIA/Model-Optimizer

👋 Hi,我擅长 AI 大模型应用落地、意识解码与 AI 开发工具链 。 💡 创业路上,用技术换时间,一起把 AI 变成生产力 🚀 >GitHub 热门: NVIDIA/Model-Optimizer 凌晨两点,你刚把跑通了的 Qwen3.…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑