一、引言:我们是如何“习惯性”写代码的?
在日常开发中,我们常常会遇到这样的需求:
“用户每操作一次,计数器 +1,同时向一个列表中添加一条记录。”
于是,很多开发者(包括曾经的我)会本能地写出如下代码:
// 原始代码:典型的“内存操作”思维
Integer turnQualityNum = session.getTurnQualityNum();
JSONArray qualityCode = session.getQualityCode();
// 自增逻辑在 Java 层
if (turnQualityNum == null) {
turnQualityNum = 0;
} else {
turnQualityNum++;
}
// 集合操作在 Java 层
if (qualityCode == null) {
qualityCode = new JSONArray();
}
qualityCode.add(issueVO.getIssueQualityCode());
// 回写并更新
session.setTurnQualityNum(turnQualityNum);
session.setQualityCode(qualityCode);
sessionService.updateById(session);
这段代码逻辑清晰、易于理解,在单线程环境下完全没问题。但一旦进入生产环境,面对高并发请求,问题就暴露了:
- 并发安全问题:多个请求同时读取
turnQualityNum
,都执行++
,导致自增丢失; - JSON 操作风险:
JSONArray
是内存对象,修改后需序列化回 JSON 字符串,容易出错; - 非原子性:读 → 改 → 写,三步操作中间可能被其他线程打断。
二、问题的本质:我们把数据库当成了“只读存储”
上述代码的思维模式是典型的 “内存中心主义”:
- 从数据库“拿数据”;
- 在内存中“加工数据”;
- 把结果“写回去”。
这就像:
“我去银行取钱 → 在家里数钱 → 再存回去”
而不是直接在银行柜台说:
“请帮我账户余额 +100 元。”
数据库不仅仅是数据的“仓库”,它更是具备强大计算能力的“服务端”。
三、思维转变:把“计算”交给数据库
真正的高并发、高可靠系统,应该让数据库自己完成“读-改-写”这一原子操作。我们只需要告诉它:“请对这条记录做这些变更”。
于是,我们写出优化后的 SQL:
UPDATE itsm_helpdesk_session
SET
-- 原子自增:null 则视为 0
turn_quality_num = IFNULL(turn_quality_num, 0) + 1,
-- JSON 原子操作:空则创建,有则追加
quality_code =
CASE
WHEN #{issueQualityCode} IS NULL OR TRIM(#{issueQualityCode}) = '' THEN
quality_code -- 空值则不更新
WHEN quality_code IS NULL THEN
JSON_ARRAY(#{issueQualityCode}) -- null 则创建新数组
ELSE
JSON_ARRAY_APPEND(quality_code, '$', #{issueQualityCode}) -- 追加
END,
update_time = NOW() -- 自动更新时间
WHERE id = #{sessionId} AND is_deleted = 0;
✅ 优化后的优势:
对比项 | 原始代码 | 优化后代码 |
---|---|---|
并发安全 | ❌ 不安全,自增丢失 | ✅ 原子操作,绝对安全 |
执行效率 | ❌ 2次 SQL(查 + 更新) | ✅ 1次 SQL,性能更高 |
数据一致性 | ❌ 可能因异常导致中间状态 | ✅ 要么成功,要么失败 |
代码复杂度 | ❌ Java 层逻辑复杂 | ✅ 逻辑集中在 SQL,简洁清晰 |
可维护性 | ❌ 修改逻辑需改 Java 代码 | ✅ 只需调整 SQL |
四、核心思维转变:从“操作数据”到“声明意图”
旧思维(内存操作) | 新思维(数据库原子更新) |
---|---|
“我先查出来,再改,再保存” | “我告诉数据库我要什么结果” |
依赖 Java 对象和集合 | 依赖数据库函数(IFNULL , JSON_ARRAY_APPEND ) |
容易忽略并发问题 | 天然避免并发问题 |
适合单机、低并发 | 适合高并发、分布式场景 |
🔑 关键认知升级:
数据库不是“哑”存储,而是“智能”计算节点。
五、适用场景
这种思维特别适用于:
- 计数器类字段(
view_count
,like_count
,turn_quality_num
) - JSON 数组的增删改(标签、操作记录、附件列表)
- 状态流转(状态 + 时间 更新)
- 任何“读 → 改 → 写”模式的操作
六、注意事项
- SQL 注入风险:使用
#{}
参数占位符,避免${}
拼接; - JSON 函数支持:确保 MySQL ≥ 5.7;
- 空值处理:在 SQL 中显式处理
NULL
和空字符串; - 事务控制:必要时仍需加事务,保证多表一致性。
七、结语:做一名“数据库友好”的开发者
从“内存操作”到“数据库原子更新”,不仅仅是代码的优化,更是一种 工程思维的跃迁。
我们应当:
- ✅ 信任数据库的能力;
- ✅ 减少不必要的内存计算;
- ✅ 让每一次更新都成为原子操作。
下次当你写“先查后改”代码时,不妨问自己一句:
“这个操作,能不能让数据库自己完成?”
如果答案是“能”,那就大胆地把计算交给它吧!
文末互动:你在项目中还遇到过哪些“本该由数据库完成”的操作却被放在 Java 层处理的案例?欢迎在评论区分享!