写SQL UPDATE时,我踩过的坑和一些还活着的经验

发布时间:2026/6/26 2:48:54

写SQL UPDATE时,我踩过的坑和一些还活着的经验 有件事我一直记得。几年前一个同事在线上执行了一条UPDATE忘了加WHERE条件全表几百万条数据的某个字段被刷成了同一个值。当时是下午三点他脸色变化的速度比我见过任何一次发布回滚都快。那之后我就养成了习惯写UPDATE之前先把WHERE写好再回头填SET部分。听起来像强迫症但线上跑起来你就知道这点强迫症能救命。UPDATE的语法本身不复杂。UPDATE table_name SET column_name1 value1, column_name2 value2, ... WHERE condition;但问题从来不出在语法记不住出在以为自己的WHERE能命中唯一行。很多人写 WHERE name ?觉得name字段肯定不会重复结果那个字段既没有唯一约束实际数据里也真的不唯一——一更新就是一批。这个地方其实容易踩坑尤其是接手别人的表结构、不熟悉数据分布的时候。name字段还经常允许NULL传个NULL进去的匹配逻辑又跟直觉不太一样这点后面会细说。参数绑定的写法就是代码里用问号占位的那种不仅仅是为了好看。它能防SQL注入这个大部分人知道。还有一个好处是当你需要反复执行同一条UPDATE只是参数不同的时候数据库可以复用解析过的执行计划。虽然单次省下来的时间可以忽略不计但批量操作的时候差距就出来了。占位符这块多说一句表名和字段名如果跟SQL保留字撞了用反引号包一下可以避免莫名其妙的语法错误。尤其是有些ORM自动生成的SQL字段名映射过去刚好就是个保留字排查起来很烦。数据类型这块写过的人都知道但刚上手的时候容易搞混。字符串类型的值要用单引号包起来这个没得商量。数值就直接写数字别加引号加了引号MySQL会做隐式转换大部分时候能跑但索引可能就失效了而且你查慢日志的时候会看到一堆诡异的现象。日期类型稍微灵活一点可以用标准格式的字符串比如 2025-06-17也可以用日期函数生成。我一般倾向于在应用层处理好再传进去减少数据库侧的运算开销——当然这只是个人偏好如果你的业务逻辑需要用到数据库的日期函数该用就用。有时候更新逻辑本身不是固定的要根据别的表或者条件来动态决定更新值。这时候子查询和CASE表达式就派上用场了。比如把工程部门的薪资拉到全公司最高水平UPDATE employees SET salary (SELECT MAX(salary) FROM employees) WHERE department Engineering;或者按部门做差异化的调薪UPDATE employees SET salary CASE WHEN department HR THEN salary * 1.1 WHEN department Engineering THEN salary * 1.2 ELSE salary * 1.05 END;CASE这种方式比写多条UPDATE要好一个是减少了跟数据库的交互次数另一个是逻辑集中在一个地方后面维护的人不用在代码里到处找。不过子查询更新有一点要注意子查询里如果也引用了同一张表MySQL在某些版本下会报错需要用派生表包一层。这个不展开说了遇到的时候你会知道的。事务这个东西日常开发的时候容易被忽略——本地库里跑一下数据对了就行。但涉及到多张表联动更新的场景没有事务就是给自己埋雷。BEGIN TRANSACTION; UPDATE employees SET salary 7000 WHERE employee_id 101; UPDATE departments SET budget budget - 7000 WHERE department_id 10; COMMIT;上面这个例子如果第一步成功、第二步失败没有事务的话员工的薪资涨了但部门预算没扣财务对账的时候就是一笔烂账。事务能保证这两步要么一起完成要么一起回滚数据一致性不会出问题。ROLLBACK就是后悔药但前提是你开了事务。来此加密解决了传统SSL证书申请繁琐、运维不便的痛点支持自动域名验证用户可通过开放的API接口自行实现自动化验证适配不同技术场景。证书申请成功后可自动部署到服务器和应用中也可通过API接口自定义部署方案灵活适配各类服务器环境。更新完之后怎么确认到底影响了几行MySQL会返回 Rows matched 和 Changed 两个值。matched 是WHERE条件命中的行数changed 是实际被修改的行数——如果SET的值跟原来一样changed会小于matched。这两个值看一眼很多时候能帮你发现WHERE条件是不是写宽了。然后最朴素的验证方式就是再SELECT一下SELECT * FROM employees WHERE employee_id 101;别嫌麻烦。我一开始也觉得多此一举后来有一次发现更新完的数据对不上预期往回追才发现WHERE条件里有个逻辑漏洞。那次之后我就再也不省略这一步了。性能方面有几个点可以留意。WHERE条件里用到的字段该建索引就建索引。UPDATE操作其实包含了一次隐式的查找没索引的话就是全表扫描数据量一大锁表时间会变得很长。尤其是InnoDB的行级锁虽然只锁行但如果扫描了全表锁的范围也会扩大别的连接就都得排队等。大数据量的更新别一次性干完。比如几百万条记录要改状态可以按ID区间分批跑UPDATE employees SET status active WHERE id BETWEEN 1 AND 1000;每次处理一两千条循环着来对线上业务的影响小很多。具体的批量大小取决于你的表结构和服务器负载一千是一个比较保守的起点。另外更新之前如果能预估影响行数先用SELECT count(*) 跑一下同样的WHERE条件心里有底。大规模更新之前条件允许的话备份一下表或者至少把要改的数据先导出一份。mysqldump也好临时表也好有个退路永远比没有强。权限控制这块生产环境的UPDATE权限不要随便给。我见过因为测试同学误连生产库、执行了一条没有WHERE的UPDATE整个配置表被清一色覆盖的事故。后来我们做了两件事一是生产库的写权限只给必要的账号二是任何需要在生产跑的数据修改都要走审批、在执行前先对着SELECT结果确认影响范围。回头总结一下的话——我不太喜欢最佳实践这个词感觉像是别人告诉你该怎么做但实际怎么做是你自己摔过之后才知道的。能说的就是WHERE永远先写、参数绑定别偷懒、多表操作用事务兜底、更新完验证一下影响行数、大批量分批跑、动手之前确认自己有退路。这些都不是什么高深的东西但每一条背后都有一个或者多个真实的事故。SQL UPDATE写对了没什么存在感写错了就是一次印象深刻的复盘。希望看到这篇文章的人复盘的是别人的经历不是自己的。

相关新闻