SQL 增删改查:和 dao 层代码怎么对应
个人主页会编程的土豆欢迎来访作者简介后端学习者❄️个人专栏数据结构与算法数据库leetcode✨那些你一个人走过的夜路终将化作照亮未来的光系列《影院票务 GO》知识点博客 · 第 7 篇对应dao/*.go中的几乎所有 SQL读完你能讲清INSERT/SELECT/UPDATE/DELETE 各干什么、Go 里怎么写、本项目每张表落在哪写在前面第 6 篇我们认识了 MySQL 的「库、表、行、主键」。但光有表结构还不够——程序真正和数据库打交道靠的是SQL 语句。SQLStructured Query Language是和关系数据库说话的语言。日常开发里说的CRUD就是四类基本操作英文SQL中文直觉CreateINSERT新增一行ReadSELECT查询数据UpdateUPDATE修改已有行DeleteDELETE删除行在本项目里这些 SQL 几乎全部写在dao/目录下。Handler 负责「接 HTTP 请求、校验参数」dao 负责「把业务动作翻译成 SQL 并执行」。本篇把四类语句讲透并逐一对照票务项目里的真实代码同时拓展WHERE、JOIN、LIKE、参数化查询、聚合与子查询。1. 先建立分层图景SQL 在项目里住哪浏览器 / JS ↓ HTTP handlers/*.go ← 解析请求、鉴权、调 dao ↓ 函数调用 dao/*.go ← 写 SQL、Scan 进 struct ↓ database/sql MySQL (ttms 库)答辩时可以这样说「我们刻意把 SQL 收敛在 dao 层。Handler 不出现裸 SQL以后换存储或加缓存改 dao 就行。」dao 目录按业务拆分文件主要表典型操作dao/user.gousers注册 INSERT、登录 SELECTdao/movie.gomovies、movie_groups电影 CRUD、搜索、推荐dao/schedule.goschedules、halls、seats场次、影厅、座位dao/order.goorders、seats下单、支付、取消多步 UPDATEdao/comment.gocomments、ratings评论 INSERT、评分 UPSERT2. INSERT创建新行2.1 语法骨架INSERT INTO 表名(列1, 列2, ...) VALUES(值1, 值2, ...)插入成功后MySQL 会给自增主键分配一个新id。2.2 Go 里怎么写标准写法是db.DB.Execres, err : db.DB.Exec( INSERT INTO users(username,password,salt,nickname,phone,role) VALUES(?,?,?,?,?,?), u.Username, u.Password, u.Salt, u.Nickname, u.Phone, u.Role, ) if err ! nil { return err } u.ID, _ res.LastInsertId()对应项目dao/user.go的CreateUser。要点?是占位符值由驱动在发送前绑定顺序必须和 SQL 里一致。LastInsertId()取刚插入行的自增 ID写回 struct后面 Session、外键引用要用。Exec适合不返回结果集的语句INSERT/UPDATE/DELETE。2.3 项目里的 INSERT 地图场景函数SQL 目标用户注册CreateUserusers新建电影分组CreateGroupmovie_groups新建电影CreateMoviemovies新建影厅CreateHallhalls新建场次CreateScheduleschedules初始化座位InitSeatsForScheduleseats循环 INSERT创建订单CreateOrderWithSeatsorders UPDATE 座位发评论CreateCommentcomments评分UpsertRatingratings见后文 UPSERT场次 座位是一个典型组合管理员在后台保存场次 →CreateSchedule插入一行 → 立刻InitSeatsForSchedule按影厅行列批量插入座位。stmt, err : tx.Prepare(INSERT INTO seats(schedule_id,row_no,col_no,status) VALUES(?,?,?,0)) for r : 1; r hall.RowsNum; r { for c : 1; c hall.ColsNum; c { stmt.Exec(scheduleID, r, c) } }这里用了Prepare预编译同一 SQL 重复执行更高效——第 8 篇会细讲。3. SELECT查询数据查询是 dao 里出现频率最高的操作。Go 标准库提供三种入口API期望行数典型用途QueryRow0 或 1 行按用户名查用户、按 id 查电影Query0 到多行电影列表、订单列表、座位图Exec不返回行仅改数据时用3.1 QueryRow Scan查单行u : models.User{} err : db.DB.QueryRow( SELECT id,username,password,salt,nickname,phone,role,created_at FROM users WHERE username?, username, ).Scan(u.ID, u.Username, u.Password, u.Salt, u.Nickname, u.Phone, u.Role, u.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return nil, nil // 查无此人不是系统错误 } return u, err关键习惯Scan的参数个数、顺序、类型必须和 SELECT 列一一对应。没有行时QueryRow返回sql.ErrNoRows要单独处理——登录「用户不存在」和「数据库挂了」是两种事。本项目约定查不到返回(nil, nil)把「无数据」和「出错」分开。3.2 Query 循环查多行rows, err : db.DB.Query(SELECT id,name,description,created_at FROM movie_groups ORDER BY id) if err ! nil { return nil, err } defer rows.Close() var list []models.MovieGroup for rows.Next() { var g models.MovieGroup if err : rows.Scan(g.ID, g.Name, g.Description, g.CreatedAt); err ! nil { return nil, err } list append(list, g) } return list, rows.Err()必须记住的三件事defer rows.Close()—— 否则连接可能泄漏。循环里每次Scan都要检查err。循环结束后检查rows.Err()—— 遍历过程中的错误会藏在这里。项目里ListGroups、ListMovies、ListOrdersByUser、ListSeats都是这个模式。scanMovies、scanOrders把重复 Scan 逻辑抽成函数避免 copy-paste。3.3 WHERE过滤条件SELECT ... FROM users WHERE username? SELECT ... FROM movies m WHERE m.status1 AND m.group_id? SELECT ... FROM schedules s WHERE s.start_time NOW()WHERE 就像筛子全表扫描太贵有索引的列主键、外键、username 唯一索引过滤更快。首页搜索dao.ListMovies会动态拼 WHEREif onlyOn { conds append(conds, m.status1) } if keyword ! { conds append(conds, (m.title LIKE ? OR m.director LIKE ? OR m.actors LIKE ?)) kw : % keyword % args append(args, kw, kw, kw) } if groupID 0 { conds append(conds, m.group_id?) args append(args, groupID) } where : if len(conds) 0 { where WHERE strings.Join(conds, AND ) } q : fmt.Sprintf(SELECT ... FROM movies m LEFT JOIN ... %s ORDER BY m.id DESC, where) rows, err : db.DB.Query(q, args...)安全边界重要拼接的是SQL 结构WHERE、AND这些固定片段。用户输入的关键词仍走?参数绑定。危险的是把用户字符串直接拼进 SQL... WHERE name name → SQL 注入。3.4 LIKE 与模糊搜索WHERE m.title LIKE %流浪%%表示任意长度任意字符。%流浪% 标题里任意位置包含「流浪」。注意前导%如%地球往往无法走普通 B 树索引数据量大时会慢。课程数据量小完全够用答辩时可说「生产环境可能用全文索引或 Elasticsearch」。3.5 JOIN多表一起查单表有时不够。电影列表要显示分组名订单列表要显示电影名、影厅名、开场时间——这些信息分散在多张表。SELECT m.id, m.title, IFNULL(g.name,), ... FROM movies m LEFT JOIN movie_groups g ON m.group_id g.idJOIN 类型含义本项目何时用INNER JOIN两边都有匹配才返回场次必须关联电影和影厅LEFT JOIN左表全保留右表无匹配则 NULL电影可以没有分组IFNULL(g.name,)分组为空时显示空字符串而不是 SQL NULL。订单查询是 JOIN 链的典型FROM orders o LEFT JOIN schedules s ON o.schedule_id s.id LEFT JOIN movies m ON s.movie_id m.id LEFT JOIN halls h ON s.hall_id h.id WHERE o.user_id? ORDER BY o.id DESC一次 SQL 取出展示所需字段避免 N1 查询查 100 个订单再循环查 100 次电影。拓展N1 问题若ListOrders只查orders表页面还要电影名有人会在循环里再GetMovie——订单多了就是「1 N 次查询」。JOIN 一次搞定是常见优化。3.6 ORDER BY / LIMITORDER BY m.rating_avg DESC, m.rating_count DESC LIMIT ?ORDER BY排序推荐列表「高分优先」。LIMIT只取前 N 条防止一次拉全库。RecommendMovies还用了子查询排除已评分电影WHERE m.status1 AND m.id NOT IN (SELECT movie_id FROM ratings WHERE user_id?)3.7 聚合AVG / COUNT / ROUND用户评分后电影表的均分和人数要更新UPDATE movies SET rating_avg(SELECT ROUND(AVG(score),1) FROM ratings WHERE movie_id?), rating_count(SELECT COUNT(*) FROM ratings WHERE movie_id?) WHERE id?子查询在 SET 里算聚合值再写回movies表——列表页直接读rating_avg不用每次 JOINratings现算。拓展MySQL 触发器也能在ratings变更时自动更新应用层更新更直观课程好讲、好调试。3.8 处理 NULLsql.NullXxx外键可空、支付时间可空时Scan 不能直接扫进int64或time.Timevar gid sql.NullInt64 var paidAt sql.NullTime row.Scan(..., gid, ..., paidAt) if gid.Valid { m.GroupID gid.Int64 } if paidAt.Valid { t : paidAt.Time o.PaidAt t }GetMovie、ListSeats、scanOrder里都有这套写法。4. UPDATE修改已有行4.1 语法UPDATE 表名 SET 列1?, 列2? WHERE 条件4.2 项目例子改电影信息db.DB.Exec( UPDATE movies SET title?,group_id?,director?,actors?,duration?,poster?,description?,status? WHERE id?, ...)锁座位下单UPDATE seats SET status?, lock_user_id?, lock_until?, order_id? WHERE id?支付成功UPDATE orders SET status?, paid_at? WHERE id? UPDATE seats SET status已售, lock_user_idNULL, lock_untilNULL WHERE order_id?取消订单 / 超时释放UPDATE orders SET status? WHERE id? UPDATE seats SET status0, lock_user_idNULL, lock_untilNULL, order_idNULL WHERE order_id?4.3 WHERE 绝不能忘没有 WHERE 的 UPDATE 会改整表——生产事故经典案例。写 UPDATE 时先问自己「这条 SQL 最多影响几行」下单锁座必须WHERE id?精确到单个座位。拓展有些团队要求 UPDATE 必须带主键或 LIMIT代码审查时强制检查。5. DELETE删除行DELETE FROM movie_groups WHERE id? DELETE FROM movies WHERE id? DELETE FROM schedules WHERE end_time ?DeleteExpiredSchedules清理过期场次若 schema 里座位对场次设了ON DELETE CASCADE删场次时关联座位自动删除。删分组时电影的group_id可能被外键设为SET NULL——电影还在只是不再属于该分组。软删除 vs 硬删除本项目场次用UPDATE status0取消软删过期场次用DELETE硬删。软删保留历史硬删节省空间。业务选型问题没有唯一正确答案。6. UPSERT有则更新、无则插入评分表(movie_id, user_id)有唯一约束同一用户对同一电影只能一条评分INSERT INTO ratings(movie_id,user_id,score) VALUES(?,?,?) ON DUPLICATE KEY UPDATE scoreVALUES(score)MySQL 专有语法。Go 里仍用tx.Exec和普通 INSERT 一样。7. 参数化查询与 SQL 注入7.1 正确做法db.DB.QueryRow(SELECT ... FROM users WHERE username?, username)驱动会把username当数据转义不会当 SQL 语法执行。7.2 错误示范q : SELECT * FROM users WHERE username username 若username是admin OR 11可能绕过校验。永远不要把用户输入用字符串拼接进 SQL。7.3 登录为什么不在 SQL 里比密码u, _ : dao.GetUserByUsername(name) if u nil || u.Password ! utils.HashPassword(pwd, u.Salt) { ... }原因密码存的是哈希盐不是明文SQL 里没法WHERE password用户输入。即使用明文也应该在应用层比较方便统一错误提示、防时序攻击等。业务逻辑放 GoSQL 只负责「按用户名取一行」。8. SELECT * 的取舍初学常写SELECT * FROM movies。本项目几乎总是显式列名SELECT m.id,m.title,m.group_id,IFNULL(g.name,),...优点表加列不会意外改变 Scan 顺序导致 bug。只取需要的列减少网络与内存。读代码的人一眼知道用了哪些字段。9. 和事务的关系预告单条 SQL 在 MySQL 里默认自动提交。多步必须原子时用事务——例如CreateOrderWithSeatsBEGIN → SELECT ... FOR UPDATE查座位并加行锁 → INSERT orders → UPDATE seats逐个锁定 COMMIT 或 ROLLBACK任一步失败整单回滚不会出现「订单建了但座位没锁」的半成品。详见第 16、17 篇。10. 和本项目代码的对应关系SQL 概念项目落点INSERTCreateUser、CreateMovie、CreateSchedule、CreateCommentSELECT 单行GetUserByUsername、GetMovie、GetSchedule、GetOrderSELECT 多行ListMovies、ListOrdersByUser、ListSeats、ListCommentsUPDATEUpdateMovie、PayOrder、锁座/释座DELETEDeleteMovie、DeleteGroup、DeleteExpiredSchedulesJOINListMovies、ListSchedules、订单/场次查询动态 WHEREListMovies、ListSchedules聚合子查询UpsertRating后更新movies.rating_avg参数?全部 dao 文件11. 常见误区「dao 就是 ORM」不对。dao 是项目里的数据访问层命名习惯我们用原生 SQL database/sql没有 ORM。「QueryRow 没结果就是 err ! nil 就报错」要区分sql.ErrNoRows正常业务用户不存在和真实错误连接断、语法错。「JOIN 越多越好」过多 JOIN 或大表 JOIN 可能慢必要时拆查询或加索引。本项目规模 JOIN 一次取展示字段是合理选择。「DELETE 和 UPDATE status0 一样」语义不同。取消场次用 UPDATE 保留记录清理过期数据用 DELETE 释放空间。「LastInsertId 可以忽略」注册、下单后常常要把新 id 写回对象或返回给前端忽略会导致后续逻辑缺 id。12. 拓展阅读12.1 索引与 WHEREWHERE username?若username有唯一索引查询是 O(log n) 级别。LIKE %关键词%往往全表扫描——数据量大要另想办法。12.2 预编译 PrepareInitSeatsForSchedule对同一 INSERT 执行「行×列」次Prepare 后重复 Exec 省解析成本。单次查询用Query/Exec即可不必凡事 Prepare。12.3 EXPLAINMySQL 的EXPLAIN SELECT ...可看是否走索引。答辩加分「若列表变慢我会 EXPLAIN 看是否全表扫描。」13. 小结可直接当口述稿SQL 四件套增删改查对应 INSERT、SELECT、UPDATE、DELETE。Go 里用 Exec 写、Query/QueryRow 读Scan 把列扫进 struct。多表展示用 JOIN搜索用 LIKE 参数绑定排序分页用 ORDER BY 和 LIMIT。用户输入必须用?占位禁止字符串拼接 SQL。多步要一致的操作放事务里——下单锁座就是典型。本项目的 SQL 都住在 dao 层Handler 只调函数不调裸 SQL。14. 思考题建议写进笔记或评论区为什么登录校验不在 SQL 里写WHERE password明文SELECT *有什么缺点本项目为什么常写列名订单列表不用 JOIN、改成先查 orders 再循环 GetSchedule/GetMovie行不行和 JOIN 方案比有何差异如何用 SQL 查出「某场次剩余空闲座位数」提示status0且schedule_id?ListComments用一次 SQL Go 内存组树和 SQL 递归 CTE 比各有什么优劣15. 下一篇预告《08 · database/sql 与连接池Ping、DSN、Open》会逐行拆db/db.gosql.Open不等于连上、Ping验证、连接池三个参数、以及为什么标准库开发仍要引入 MySQL 驱动

相关新闻

AI模型推理延迟优化:从剪枝量化到硬件加速

AI模型推理延迟优化:从剪枝量化到硬件加速

1. AI模型推理延迟的现状与挑战在当前的AI应用场景中,推理延迟已经成为制约系统性能的关键瓶颈。以我过去参与的智能客服项目为例,当响应时间超过300ms时,用户满意度就会显著下降。特别是在实时性要求高的场景,如自动驾驶的物体识…

2026/7/27 6:09:16阅读更多 →
整理infrared热点(二)

整理infrared热点(二)

#long-distance-relationship #突然很确信我们有些地方有点像蜘蛛侠与格温 三、具有尺度和位置敏感性的红外小目标检测Infrared Small Target Detection with Scale and Location Sensitivity 插曲 去了解一下特征融合-发现是深度学习里的 【极致中配】Stanford CS231N 计算…

2026/7/27 6:07:16阅读更多 →
TMS320F28xx硬件设计实战:从电源、时钟到PCB布局的可靠性指南

TMS320F28xx硬件设计实战:从电源、时钟到PCB布局的可靠性指南

1. 项目概述:从芯片到稳定系统,硬件设计的挑战与应对在嵌入式控制领域,尤其是电机驱动、数字电源和新能源逆变器这些对实时性和可靠性要求极高的场景,德州仪器(TI)的TMS320F28xx/F28xxx系列数字信号控制器&…

2026/7/27 6:07:16阅读更多 →
AI图纸识别技术解析与制造业应用实践

AI图纸识别技术解析与制造业应用实践

1. 制造业图纸识别的痛点与AI破局之道在机械制造和工程设计领域,图纸是传递技术信息的核心载体。传统设计实践中,工程师们为了节省纸张和提高工作效率,常常将多个零件的视图、尺寸标注和技术要求全部压缩在一张图纸上。这种"全家福"…

2026/7/27 7:45:23阅读更多 →
openMenus AI框架本地化部署与模型管理实战

openMenus AI框架本地化部署与模型管理实战

1. 项目概述openMenus作为一款新兴的AI应用框架,其本地化部署能力正在成为开发者社区的热门话题。最近半年,从RAGflow到DeepSeek,各大AI框架都在强化本地部署功能,而openMenus的独特之处在于它同时支持预训练模型和轻量化定制模型…

2026/7/27 7:45:23阅读更多 →
Jenkins中Allure报告历史趋势丢失的排查与修复指南

Jenkins中Allure报告历史趋势丢失的排查与修复指南

1. 项目概述:当Allure报告在Jenkins中“失忆”如果你和我一样,在团队里负责CI/CD流水线的维护,那么Allure测试报告绝对是提升测试可视化、定位问题效率的神器。它能将冷冰冰的自动化测试结果,变成一张张清晰、美观的图表和趋势图。…

2026/7/27 7:45:23阅读更多 →
Redis 五大数据类型精讲(List 列表)

Redis 五大数据类型精讲(List 列表)

一、List 类型介绍List 是有序可重复的字符串列表,底层基于双向链表实现,头尾操作极快,中间查询较慢。元素有序、可重复、支持左右进出,适合队列、栈结构场景。二、核心常用命令1. 入队操作LPUSH key v1 v2 # 左侧入队&#xff08…

2026/7/27 7:45:23阅读更多 →
Dockerfile实战:从零构建Nginx镜像并解决容器内Yum仓库配置

Dockerfile实战:从零构建Nginx镜像并解决容器内Yum仓库配置

在容器化部署的实践中,我们常常会遇到一个核心需求:如何将一个现有的应用或服务,连同其运行环境,打包成一个标准、可移植的镜像。很多教程会直接使用docker commit命令,但这通常不被推荐用于生产环境,因为它…

2026/7/27 7:45:23阅读更多 →
天气丹水乳料体拿货,别被低价料体坑了口碑

天气丹水乳料体拿货,别被低价料体坑了口碑

拿着韩系高端礼盒图找工厂打样,料体成本压到15块一公斤,结果上架三天差评刷屏——发红、搓泥、闻着有股工业香精味。这事我一年经手几十个案子,说白了,就是没摸清同源架构的工艺底牌。▼ 源头车间质检备案与合作授权说明 ▼韩系宫…

2026/7/27 7:43:23阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/27 1:14:34阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/27 1:14:52阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/27 1:14:56阅读更多 →
SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

SPI实战指南:从时钟模式到寄存器配置,解决嵌入式通信难题

1. 项目概述:从寄存器手册到实战指南 如果你手头有一份类似德州仪器(TI)TMS320x240xA系列DSP的SPI模块技术手册,看着里面密密麻麻的寄存器位定义、时序图和公式,是不是感觉头大?这份资料虽然权威&#xff0…

2026/7/27 0:00:24阅读更多 →
【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于springboot的水果购物管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/27 0:00:24阅读更多 →
2007-2023年各市区县生态文明建设示范区DID

2007-2023年各市区县生态文明建设示范区DID

数据简介 自改革开放以来,我国依赖高投入、高资源消耗和高污染等传统发展模式实现了经济短期内的快速增长, 然而这也导致了严重的生态环境危机。因此,国家有力于推动企业高质量经济发展,协同生态保护的方针,从而从201…

2026/7/27 0:00:24阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/25 23:03:25阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →