CRUD这件小事,99%的人都误解了

2026-06-22 13 0

CRUD这件小事,99%的人都误解了

干过后端开发的都知道,CRUD这四个字,入门花一天,深入耗一生。它是软件世界里最被低估的工程活动——没有之一。

我见过太多人,写了三年代码,张口闭口"高并发"、"分布式",结果让他写一个靠谱的订单创建接口,脑子里想到的第一件事是"ORM怎么用来着"。

今天不聊虚的,来讲几个真实踩坑故事,聊聊CRUD里那些真正值得琢磨的东西。

1. 创建:你的INSERT真的只是INSERT吗?

先说个我亲手经历的事故。

某个看似人畜无害的功能:创建订单。逻辑也很简单——接收参数,插入订单表,返回成功。代码写出来大概长这样:

// 这是绝大多数人的写法
public function createOrder($data) {
    return Order::create($data);
}

上线第一周,数据库磁盘空间开始以每天几十GB的速度增长。一查,审计日志表里每创建一条订单就插入一条记录,而且这个日志表从来没清理过。

但最离谱的是什么你知道吗?这个审计日志功能是两年前某个外包写的,当时他觉得"记录操作日志是个好习惯"。好习惯是好习惯,但没有清理机制的日志就是数据垃圾桶。

这个故事教给我什么?创建的本质不是INSERT,而是一次数据边界的确认。你要问自己:这次写入会触发什么?监听器会不会跑?关联数据会不会被创建?缓存会不会失效?这些才是创建接口的核心问题。

我现在写任何创建接口,第一反应不是想INSERT什么字段,而是想这个操作的影响链有多长。高手写创建,不是不触发任何东西,而是清楚地知道会触发什么,以及如何处理。

2. 查询:SQL本身从来不是瓶颈

再说一个被问过无数次的问题:为什么我的查询在压测时很快,上线就炸?

我见过一个经典案例。有人要查订单列表,需求是"筛选出状态为已支付、创建时间在某个区间的订单",还要关联用户信息做展示。SQL写出来大概这样:

SELECT o.*, u.name, u.email 
FROM orders o 
LEFT JOIN users u ON o.user_id = u.id 
WHERE o.status = 'paid' 
  AND o.created_at BETWEEN '2026-01-01' AND '2026-06-01'
LIMIT 20;

索引建了,JOIN也用了,压测数据100万条,轻松跑在50毫秒以内。上线。

三个月后数据量到了一亿条,这个接口在某些时间段开始超时。最后怎么解决的?

思路翻转一下:先从用户表筛选出符合条件的用户ID列表,再去订单表查。而且这次查询变成了IN查询而不是范围扫描,索引完全生效。查询从全表扫描变成了精准定位。

查询优化的本质从来不是找到一条"神奇的SQL",而是让你的查询贴合数据的实际分布。

当你数据量小的时候,怎么查都快。当你数据量大了,真正管用的优化往往不是SQL层面,而是数据模型和访问路径的设计。我见过最离谱的优化方案是什么知道吗?把一个大表拆成历史表和热表,热表只保留最近三个月的数据——查询性能立降95%,代码一行没改。

3. 更新:并发更新丢失才是真正的噩梦

更新是CRUD里最容易出生产事故的操作。

有一个经典场景:用户修改文档,用户A和用户B同时打开同一份文档,A改完保存,B也改完保存。结果A的改动被B覆盖了。不是什么复杂场景,但很多系统第一次遇到这个问题时都是一脸懵。

解决方案教科书上教过:乐观锁。给表加个version字段,更新时比对版本号:

UPDATE orders 
SET status = 'completed', version = version + 1 
WHERE id = 123 AND version = 5;

影响行数为0就说明有人改过了,重试。道理很简单,但真正遇到高并发场景,乐观锁的重试代价会让你怀疑人生。

我经历过一次真实的"乐观锁风暴":某个抢优惠券活动,3000并发请求同时去更新一张库存表,乐观锁版本号冲突率高达99%。结果接口超时,前端开始重试,重试又加剧冲突,最后整个服务雪崩。

最后怎么解决的?换成粗粒度锁——用Redis做一个分布式锁,把并发控制从数据库层移到缓存层,同时把库存扣减逻辑从"读-改-写"三步变成Lua脚本原子操作。数据库的乐观锁退化为最后兜底,线上流量根本碰不到它。

更新操作最值钱的思考方式,不是想"怎么更新成功",而是先想清楚"并发冲突时怎么办"。

4. 删除:逻辑删除还是物理删除,这不是一个技术问题

删除可能是CRUD里最"简单"的操作了,DELETE FROM orders WHERE id = 123,对吧?

错。这个语句背后藏着无数的坑。

这个订单有没有关联的附件表?有没有关联的日志表?有没有被其他表的外键引用?缓存里有没有?搜索引擎的索引里有没有?

级联删除配错了,数据变成孤岛;删得太干净,审计查不到账;删得太保守,系统里一堆幽灵数据。

我的经验:所有删除操作默认做软删除。设一个deleted_at字段标记删除时间,给它加索引。真正需要物理删除的场景少之又少,而且一定要业务上确认之后再执行,可以脚本执行,但必须有人负责。

删除的核心问题是:搞清楚所有数据依赖之后,按正确顺序清理。这不是SQL问题,是业务流程问题。

5. CRUD之外的东西,才是真正分水岭

现在流行微服务,CRUD感觉成了过时的代名词。聊什么消息队列、分布式事务、链路追踪才显得有水平。

但你仔细想想:微服务之间数据怎么流通?最终还是靠CRUD。操作Redis缓存?本质上还是CRUD。DDD里的仓储层?说到底也是CRUD。

CRUD并没过时,过时的是只会写SQL的人。

真正的高手写一个创建订单接口,考虑的东西包括:

  • 幂等性如何保证——重复提交怎么办
  • 事务边界在哪里——哪些操作必须在同一个事务里
  • 并发安全——会不会出现超卖
  • 事件是否正确触发——订单创建后,库存服务要通知、积分服务要同步
  • 缓存一致性——写完数据库,缓存要不要更新
  • 监控是否完整——慢查询有没有被记录
  • 可观测性——请求能不能追踪到某一次数据库操作
  • 优雅降级——数据库挂了,接口能不能返回有意义的错误而不是500

这才是"写接口",不是"写CRUD"。

写在最后

区分一个人是工程师还是代码搬运工,就看他怎么对待这四个字母。

搬运工说:CRUD不就是查查数据库嘛。

真正的工程师知道:CRUD的核心从来不在于SQL本身,而在于对数据一致性、并发安全、业务完整性的理解。SQL只是表达工具,思维方式才是真正的门槛。

所以别再嘲笑CRUD了,你可能连表面功夫都没做到位。

积点德吧各位。

相关文章

写了5年后端,我总结了一套API设计的防坑指南
我写API这十年:见过的烂设计能绕地球三圈
写API这事儿:有人写得跟情书一样优雅,有人写得跟遗书一样潦草
ORM这个温柔的陷阱,毁掉了无数年轻程序员的数据库功底
写API就是在写命:那些年我们一起踩过的设计坑
为什么你的API总被吐槽?来自一线工程师的7条血泪教训

发布评论