UPDATE语句本身不丢失数据,但WHERE缺失、SET写死值、NULL处理不当三类失误会导致误改;必须校验WHERE命中索引、启用sql_safe_updates、显式过滤空值、严格实现乐观锁、检查rowsAffected。

直接说结论:UPDATE 语句本身不会“丢失数据”,但写法不当会导致原有字段被意外覆盖、清空或全表误改——真正要防的,是 WHERE 条件缺失、SET 子句写死值、或对 NULL 值不做判断这三类操作失误。
WHERE 条件漏写或不精确,导致多行/全表被改
这是最致命也最常见的问题。比如你本想只更新某条订单的状态,却写了:
UPDATE orders SET status = 'shipped';
结果整张表的 status 全变成 shipped,备份都没来得及恢复。
- 上线前必须用
EXPLAIN确认 WHERE 是否命中主键或唯一索引;没索引的字段(如status = 'pending')慎用于FOR UPDATE或高频 UPDATE - 开发环境开启
sql_safe_updates=1,MySQL 会强制要求带 WHERE 或 LIMIT - 批量更新时,别用
WHERE id_demande = ?这种宽泛条件去改关联表,它会把所有匹配的记录全刷成同一个值——正确做法是先DELETE旧关联,再INSERT新数据
SET 子句里硬写 NULL 或空字符串,覆盖原值
前端传参缺失时,后端若不加判断就拼进 SQL,就会把非空字段清掉。例如:
UPDATE user_settings SET font_size = ?, bg_color = ? WHERE name = ?;
如果 font_size 是 None,而你把它当 NULL 绑定进去,那一列就真变 NULL 了。
- 应用层需显式过滤:只把
value is not None的字段加入SET子句,避免无意义赋值 - 不要依赖数据库默认值兜底——
DEFAULT只在 INSERT 时生效,UPDATE 不触发 - 字段类型为
NOT NULL时,硬塞NULL会直接报错,反而暴露问题;但允许NULL的字段被静默覆盖,更难排查
并发场景下后写覆盖前写,version 字段形同虚设
加了 @Version 注解或写了 version = version + 1,不代表乐观锁就生效了。漏掉任一环节,等于裸奔:
-
WHERE里必须同时带id = ? AND version = ?,只写id就失效 -
SET里必须更新version = version + 1,否则下次WHERE version = ?永远不匹配 - 执行后必须检查
executeUpdate()返回值,rowsAffected == 0就得立刻中断,不能继续走下游逻辑 - MyBatis 的
@Update如果没手动拼AND version = #{version},或者 JPA 实体里@Version字段是short类型,高并发下溢出变负数,WHERE 就可能意外命中旧记录
真正容易被忽略的点是:影响行数为 0 时,MySQL 和 PostgreSQL 都不会报错,只会安静返回。框架默认把它当“成功”处理,结果库存没扣、座位没锁、配置没生效,业务却以为一切 OK——这个静默失败,比报错更危险。











