MySQL UPDATE语句全解析:从基础语法到性能优化与锁机制
发布时间:2026/9/11 3:58:57 锦皓数字建站

有人在生产库上执行了一条UPDATE忘了加WHERE条件全表数据被改了。这种事在开发圈里隔三差五就能听到一次而且每发生一次基本就有一个DBA要加班到深夜有一个开发要写事故报告。说实话我做了这么多年后端和数据库相关的活儿SELECT用得最多INSERT也熟练但真到UPDATE这里反而容易栽跟头。原因很简单——UPDATE是改变数据状态的语句它的影响范围由WHERE条件决定而很多人写WHERE的时候脑子里想的还是SELECT那一套逻辑。SELECT查错了顶多结果不对重查一次就行UPDATE写错了数据就变了甚至没有后悔药可以吃。这篇内容我想把MySQL里UPDATE的底细拆开讲一遍从最简单的单表更新到多表关联更新再到性能优化和事务锁的相互作用把那些文档里不会明说、但实战里一定会遇到的细节都翻出来说清楚。适合已经会写基础SQL、想把手上的更新操作写得更稳更快的开发者也适合正在为慢UPDATE和死锁头疼的运维或DBA参考。1. 内容整体设计与思路拆解1.1 为什么UPDATE比SELECT更容易引发事故先聊一个很实际的问题为什么同一个开发者写SELECT很熟练一到UPDATE就出问题核心原因是心智模型不同。SELECT是只读操作它不改变任何状态所以在思考方式上你可以“先试后看”查错了再换一个条件没有任何成本。但UPDATE是写操作它一旦执行成功就会直接修改磁盘上的数据页而且在默认的autocommit模式下一条UPDATE就是一个隐式事务执行完就提交几乎没有回退的窗口。再深入一层UPDATE的执行路径比SELECT多了一个环节。MySQL执行一条UPDATE时Server层先要根据WHERE条件找到匹配的记录这一步和SELECT完全一样走索引也好全表扫描也罢逻辑都一样。但找到记录之后还要额外做的事情就多了给记录加锁、读取当前值、在内存中修改、写undo log做版本链、写redo log做崩溃恢复、标记binlog用于主从同步。整个过程比SELECT重得多任何一个环节出问题都可能影响线上稳定性。所以在学习UPDATE的时候不能只盯着语法看要把“影响范围”—“锁范围”—“性能成本”这三件事放在一起考虑。这也是我这篇内容的整体设计思路先保证你写得对再保证你写得稳最后保证你写得快。1.2 一条UPDATE在MySQL内部到底经历了什么很多开发者在排查慢UPDATE时习惯性地把锅甩给“SQL写得慢”但真实情况往往更复杂。为了让你后面理解锁机制和性能优化时不至于一头雾水这里有必要把UPDATE的执行过程完整过一遍。第一步是连接器接收SQL进入Server层。Parser解析语法Preprocessor检查表和字段是否存在Optimizer决定使用哪个索引这一步和SELECT完全一致。差异从执行器开始。执行器根据优化器选择的执行计划逐行读取满足WHERE条件的记录。对于每一行InnoDB存储引擎会先尝试加锁加锁的方式取决于当前事务的隔离级别和这条SQL是否走了索引。如果走的是主键索引加的是主键记录锁如果走的是二级索引除了加二级索引记录锁还要回表去主键索引里把对应的主键记录也锁住。如果没走索引那就是全表扫描每一行都被加上锁性能直接拉胯。锁拿到之后InnoDB把这行记录的最新值读到内存中按照SET子句的规则算出新值写入内存中的记录。同时旧值会被写入undo log形成版本链这就是MVCC能够支持其他事务读旧版本数据的基础。然后InnoDB写redo log把这次修改记录成物理日志确保即使内存里的数据还没刷盘MySQL突然崩溃重启后也能通过redo log恢复不会丢数据。如果binlog是开启状态Server层还会把这条UPDATE写进binlog用于复制和备份恢复。到这里你应该能理解两个关键结论第一UPDATE的成本绝对不只是“改一行数据”那么简单它涉及到加锁、写undo、写redo、写binlog数据量一大每一层都是性能瓶颈第二WHERE条件能不能走索引直接决定了锁的粒度是行锁还是表锁级别的全表锁这是后面讨论优化时反复要提到的核心点。2. 核心细节解析与实操要点2.1 UPDATE基础语法别小看这三段式MySQL里UPDATE的基础语法长这样UPDATE table_name SET column1 value1, column2 value2, ... WHERE condition;看起来简单但三部分各有坑。第一表名部分。可以写单表也可以写UPDATE t1 JOIN t2 ...做多表更新还能在表名后面加别名比如UPDATE orders AS o SET ...。别名这个东西平时不起眼但一旦涉及多表关联或子查询没有别名几乎没法写。第二SET部分这里有几个容易忽略的细节可以同时更新多个字段用逗号分隔这一点没什么好说的。更新的值可以是常量、变量、表达式甚至可以是另一个字段的值。比如SET num num 1和SET name CONCAT(last_name, first_name)都是合法写法。在MySQL中SET子句的求值顺序是从左到右的。什么意思呢就是如果你写SET a b, b a执行完之后a拿到的是b的旧值b拿到的是a的旧值。这一点和部分其他数据库不太一样容易踩坑。第三WHERE部分这是整个UPDATE的安全命门。WHERE缺了或者谓词写得太宽泛影响范围就会失控。一个比较保守但实用的习惯是执行UPDATE之前先把WHERE条件复制到SELECT里跑一遍看看返回多少行确认这个范围是你想要的再加UPDATE前缀执行。2.2 WHERE缺了会怎样一个必须养成的防御习惯先别急着往下看老实回答一个问题你有多少次是在写完UPDATE之后想也没想直接点了执行我自己早年就吃过亏。当时在测试环境改一批配置数据SQL大概是UPDATE config SET status 1 WHERE app_id 10086 AND env prod看起来条件挺全但实际上app_id字段在表里不是索引列env也没有索引整张表直接被全表扫描虽然结果没错但在那个数据量级下锁住了全表的行直接把线上读请求堵了几秒钟。那一瞬间我才真正理解UPDATE的性能问题不只是“慢”的问题是“锁”的问题锁的范围越大对并发的冲击就越大。所以在生产环境执行UPDATE之前我给自己立了几条规矩WHERE条件里的字段优先保证有索引至少要让MySQL能用上索引来缩小扫描范围。先用相同WHERE条件跑SELECT COUNT(*)确认影响行数符合预期。如果影响行数超过一定阈值比如几千行先问问自己是不是真的要一次改这么多能不能分批在事务里执行先不提交看一眼SELECT ROW_COUNT()和实际数据是否正确确认无误再COMMIT。这几条不是DBA的专属要求后端开发同样适用。毕竟很多时候更新线上数据前端不会给你弹窗提示“你确定吗”。2.3 ORDER BY和LIMIT的巧妙用法谁说UPDATE不能精准控制影响行数很多人不知道MySQL的UPDATE其实是支持ORDER BY和LIMIT的。这两个子句不是摆设在某些场景下非常管用。UPDATE t SET status done WHERE status pending ORDER BY id LIMIT 100;这条SQL的意思是从满足status pending的记录里按id从小到大排序只更新前100条。用这个写法可以很优雅地实现“批量处理一批处理不完下一轮再处理”的逻辑避免一次性锁住大量数据。LIMIT在UPDATE里的另一个作用是安全兜底。假设你写了一条UPDATE本意是更新一条记录但WHERE条件因为某些原因匹配到了很多行加上LIMIT 1就可以把影响范围死死卡在一行上。当然这只是一个补救措施真正该做的是把WHERE条件写精确。ORDER BY在UPDATE里的意义主要是和LIMIT配合决定“先处理哪些行”。比如有一个任务队列表多个worker进程同时在消费每个worker执行UPDATE task SET status processing WHERE status pending ORDER BY priority DESC, id ASC LIMIT 10可以确保优先级高的任务先被取走同时由于每批只取10条锁的范围很小并发消费时不容易互相阻塞。2.4 affected rows为0不代表没执行ROW_COUNT背后的信息量执行完UPDATE之后客户端会返回一个affected rows数。这个数字初看很简单但细究起来有一些坑。MySQL默认情况下如果SET子句设置的新值和旧值完全一样affected rows会返回0即使这条记录确实被“处理”了。也就是说UPDATE t SET num 1 WHERE id 1如果id1那行的num本来就是1结果返回的受影响行数是0而不是1。原因在于MySQL做了一个优化如果新值和旧值没变化就不实际写入避免产生无意义的redo log和binlog。这个行为在业务里会引发一个常见困惑程序里用受影响行数来判断“更新是否成功”结果明明数据是对的却拿到了0然后误判为失败。解决方法有两个一是在连接串里加上useAffectedRowstrue让驱动返回“匹配到的行数”而不是“实际变更的行数”二是在业务逻辑里不要依赖affected rows做唯一判断而是先SELECT确认再决定。反过来也有一个有趣的技巧如果想让affected rows无论如何都返回匹配行数可以在SET里做一个“无意义但会改变”的操作比如SET num num 0。但说实话这属于偏门写法不建议在正式代码里用容易让后来维护的人一头雾水。3. 实操过程与核心环节实现3.1 单表更新从入门到规范化的三个版本先看一个最常见的场景用户表里有个last_login_time字段用户登录之后要更新它。新手写法UPDATE users SET last_login_time NOW() WHERE username lisi;这个写法在username有唯一索引的情况下没什么问题但如果username没有索引那就等于全表扫描。更规范的做法是先确认username的唯一性约束或者干脆用主键id来更新UPDATE users SET last_login_time NOW() WHERE id 12345;再看一个稍微复杂一点的例子订单表里需要把所有已支付但未发货的订单发货时间统一设置为当前时间同时把状态改为“待发货”。UPDATE orders SET ship_time NOW(), status pending_shipment WHERE status paid AND ship_time IS NULL;这里有一个细节值得注意ship_time IS NULL这个条件很重要。如果没有它每跑一次这个SQL所有已支付订单的ship_time都会被刷新成当前时间即使之前已经设置过了。加了IS NULL之后这条UPDATE就具备幂等性——跑多少次都只影响第一次执行时的“待处理”数据。幂等性在定时任务和消息重试机制里是非常重要的设计思想能避免很多重复操作带来的脏数据。3.2 多表关联更新JOIN和子查询两种写法怎么选现实业务里很多时候要更新的一张表的数据但筛选条件却要在另一张表里判断。比如把所有在黑名单表里的用户在用户表里标记为禁用。第一种写法是用JOINUPDATE users u JOIN blacklist b ON u.id b.user_id SET u.status disabled;第二种写法是用子查询UPDATE users SET status disabled WHERE id IN (SELECT user_id FROM blacklist);两种写法都能达到目的但有几点差异需要注意。第一JOIN写法更直观而且可以发现“一条用户记录匹配到多个黑名单条目”的情况如果blacklist里user_id不是唯一的JOIN写法可能会造成同一行被更新多次虽然结果相同但会影响affected rows的计数。子查询写法天然避免了这个问题因为IN子查询做了去重。第二子查询写法有一个经典的MySQL限制——你不能在UPDATE一张表的时候子查询里再引用同一张表。例如-- MySQL会报错You cant specify target table users for update in FROM clause UPDATE users SET status disabled WHERE id IN (SELECT id FROM users WHERE last_login_time 2020-01-01);这条SQL在MySQL里直接报错赋值也拿不到。解决办法是把子查询再包一层临时表UPDATE users SET status disabled WHERE id IN ( SELECT id FROM ( SELECT id FROM users WHERE last_login_time 2020-01-01 ) AS tmp );当然如果你用的是MySQL 8.0还可以用WITH语法做CTE但核心逻辑是一样的先查出来放到临时结果集里再供外层UPDATE引用。第三性能上JOIN和IN子查询在优化器手里可能会被转换成类似的执行计划但在数据量差异大的情况下会有差别。如果子查询的结果集很小比如只有几条IN一般是好的选择如果关联的大表且关联字段都有索引JOIN写法更可控。实际项目中我会先在测试环境用EXPLAIN看一眼执行计划再决定哪种写法上线。3.3 多表关联更新的另外两种写法UPDATE多表语法除了JOIN和子查询MySQL还有一套原生的多表UPDATE语法UPDATE users u, blacklist b SET u.status disabled WHERE u.id b.user_id;这种写法本质上是把UPDATE ... JOIN ... SET换成了逗号分隔的隐式连接。我个人不太推荐这种写法因为WHERE条件既承担着连接条件的职责又承担着过滤条件的职责混在一起可读性差而且一旦遗漏连接条件就会变成笛卡尔积更新后果不堪设想。JOIN关键字至少有明确的ON子句把连接逻辑和过滤逻辑分开犯错概率低很多。这里还要提醒一个细节多表UPDATE时SET子句里的字段必须带上表名前缀比如u.status或者users.status否则如果多个表里有同名字段MySQL会报Column status in field list is ambiguous错误。3.4 实战更新后用一条SELECT校验数据一致性更新完数据不要急着切走花10秒钟做一次校验比事后写事故报告划算多了。比如执行完前面那个“黑名单用户禁用”的操作后我会立刻跑一条核对SQL确认结果一致SELECT COUNT(*) AS disabled_expected FROM blacklist b JOIN users u ON u.id b.user_id; SELECT COUNT(*) AS disabled_actual FROM users WHERE status disabled;如果两个数字对不上至少能第一时间知道UPDATE没按预期生效而不是等业务反馈才发现。同样如果UPDATE是在事务里执行的提交之前先在同一连接里查一下更新后的数据比提交之后再查要安全得多毕竟一旦提交了就真的回不去了。4. 性能优化让慢UPDATE里的每一步都有据可循4.1 更新一条记录为什么也会慢从执行计划说起遇到慢UPDATE第一步永远是EXPLAIN而不是猜。举个例子现在要执行UPDATE orders SET status shipped WHERE order_no 202405010001;先跑EXPLAIN SELECT id FROM orders WHERE order_no 202405010001;看type字段的取值。如果type是ALL说明MySQL在扫描全表即使这条SQL最终只更新了一行扫描和加锁的范围却是整张表。如果type是ref或range说明索引起了作用扫描范围小得多。如果type是eq_ref或const那走的是唯一索引或主键性能最优。把考察对象锁定在WHERE谓词上的字段看它有没有索引或者索引有没有被用上这是排查慢UPDATE的第一步。这里有类特殊场景要特别小心如果WHERE条件里对索引列做了计算或函数操作比如WHERE DATE(create_time) 2024-05-01MySQL就没法用create_time上的索引因为优化器需要在每一行上先算一次DATE函数才能判断是否匹配。解决办法是改写为区间查询WHERE create_time 2024-05-01 00:00:00 AND create_time 2024-05-02 00:00:00这种写法可以充分利用索引扫描行数从全表骤降到区间内行数。4.2 大批量更新怎么不锁死线上业务三种分批策略线上要更新几百万行数据的时候千万别一条UPDATE梭哈。一条大UPDATE会持有大量行锁执行期间还可能阻塞其他事务的读写导致整个业务停顿。分批更新是必修课。第一种分批策略是按主键范围切分。适合主键分布相对均匀、业务允许按id区间处理的场景UPDATE t SET status 1 WHERE id BETWEEN 1 AND 1000 AND status 1; UPDATE t SET status 1 WHERE id BETWEEN 1001 AND 2000 AND status 1;第二种策略是按WHERE条件加LIMIT循环适合没有连续主键或无法按范围切分的场景UPDATE t SET status 1 WHERE status 0 ORDER BY id LIMIT 1000;在应用层写个循环每轮执行完检查affected rows是否小于1000小于就说明处理完了退出循环。这种方式每轮只锁1000行对在线业务影响小很多同时每轮之间可以sleep几十毫秒给其他事务留出窗口。第三种策略是落在临时表上做JOIN更新。先把要更新的主键集合插入临时表再通过临时表JOIN原表更新分批导入临时表更新完清空临时表下一批再来。这种方案的优点是幂等性好缺点是需要额外的存储和步骤适合数据链路更严苛的场景。无论选哪种策略都要记得给每批更新设置合理的间隔并且监控主从延迟。大批量更新产生的binlog在从库上重放时同样耗时如果从库延迟过高读流量就会受到影响。4.3 索引不是越多越好UPDATE场景下的索引代价写SELECT的时候大家习惯了“多建索引防慢查询”但在UPDATE场景里索引是一个需要权衡的变量。原因很简单每增加一个二级索引就意味着每次INSERT和UPDATE时都要额外维护这个索引的B树结构。举个例子一张表上有5个索引其中3个二级索引都引用了某个字段现在执行UPDATE t SET status 1 WHERE id 100。InnoDB除了要更新主键索引里的数据行还要检查status字段的旧值和新值是否一致。如果status是某个二级索引的组成列而新值和旧值不一致InnoDB必须先删除索引中的旧条目再插入新条目这个操作叫“索引分裂”或“索引重组”成本是比较高的。所以在设计表结构时索引不是越多越好。尤其是那些“可能用得上”的低频索引它们在UPDATE上的副作用可能远大于它们在SELECT上的帮助。我的建议是定期用SHOW INDEX FROM t和慢查询日志核对把那些长期没有被优化器选中的索引逐个删掉给写操作减负。4.4 修改主键值到底行不行还有一个经常被问到的操作能不能直接UPDATE主键字段的值语法上可以比如UPDATE users SET id 20000 WHERE id 10000。但如果这张表有多个外键或二级索引InnoDB需要同步更新所有索引条目成本极高而且容易引发死锁。更严重的是如果id字段被其他表的业务逻辑引用虽然没有外键约束改完主键后关联数据就错乱了。所以我在实践中几乎从不更新主键值。真的需要重建主键数据时宁可先INSERT一条新记录再UPDATE或DELETE旧记录也绝不在原记录上直接改主键。这不是语法能不能的问题是值不值得的问题。5. 事务、锁与死锁UPDATE躲不开的并发问题5.1 从一条UPDATE看InnoDB的行锁和间隙锁先看一个经典场景事务A执行UPDATE t SET status 1 WHERE id 10事务B同时执行UPDATE t SET status 2 WHERE id 10。会发生什么答案是B会阻塞直到A提交或回滚。原因是InnoDB在默认的REPEATABLE READ隔离级别下对id 10这一行加了排他锁X锁同一行上的X锁互斥B只能等待。但如果WHERE条件是一个范围比如WHERE id BETWEEN 10 AND 20InnoDB加的就不只是这11条记录上的记录锁还会在索引范围内插入间隙锁也就是在id为10到20这个区间内的“空洞”上也加锁。间隙锁的作用是防止其他事务在这个范围内插入新记录从而避免幻读。但也正因为间隙锁的存在两个事务如果各自持有一部分间隙锁又在等对方释放另一部分就可能陷入死锁。举例来说-- 事务A UPDATE t SET status 1 WHERE id BETWEEN 10 AND 15; -- 事务B UPDATE t SET status 1 WHERE id BETWEEN 15 AND 20;如果A先锁住了10-15B锁住了15-20然后A想再往右扩展B想往左扩展两边都在等对方释放锁死锁就出现了。InnoDB检测到死锁后会回滚其中一个事务并抛出Deadlock found when trying to get lock; try restarting transaction错误。业务代码里遇到这个错误正确的处理方式是捕获异常后重试事务而不是直接放弃。为了减少死锁发生的概率比较有效的习惯包括事务里的SQL都按相同的顺序访问表比如都先更新id小的记录再更新id大的事务尽量短小减少锁持有时间UPDATE的WHERE条件尽量走唯一索引避免间隙锁变成主要矛盾。5.2 一条UPDATE没提交前其他会话看到什么这里要说一下UPDATE和MVCC的交互。假设事务A执行UPDATE t SET num 100 WHERE id 1但还没提交。事务B这时候执行SELECT num FROM t WHERE id 1在REPEATABLE READ隔离级别下B看到的是旧值比如原来的50而不是A改出来的100。这是MVCC的功劳——B读取的是undo log里版本链上的旧快照不会读到未提交的修改。但要注意一个容易被误解的点如果B执行的是SELECT ... FOR UPDATE或者UPDATE那就不是普通的快照读了而是一条当前读会尝试对id1这行加锁。这时候B会阻塞直到A提交后B才能继续读取到的也是A提交后的新值100。这个差异在实际业务里很重要——普通的SELECT性能好、不会被写阻塞但如果你需要最新的已提交数据就必须考虑用当前读同时也要承受可能被锁等待的代价。5.3 锁等待超时怎么优雅地处理UPDATE阻塞InnoDB有个参数叫innodb_lock_wait_timeout默认是50秒。也就是说事务B尝试获取一行锁时最多等50秒超时就会报Lock wait timeout exceeded错误。50秒这个默认值其实并不适合所有场景。如果业务里事务都很短50秒太长一个慢事务会把大量请求堵在那里更合理的做法是调低到5秒或10秒让慢事务的请求快速失败给业务方一个“稍后再试”的信号而不是让用户一直转圈。可以通过下面这条SQL查看当前值SHOW VARIABLES LIKE innodb_lock_wait_timeout;在线调整的话SET GLOBAL innodb_lock_wait_timeout 10;但只调这个参数还不够。真正治本的方法是分析information_schema.INNODB_TRX表看看到底是哪个事务一直没提交占用着锁不放。很多时候锁等待的根源不是锁本身而是业务代码里忘了提交事务或者事务里夹杂了外部HTTP调用导致事务被拉得又长又宽。6. 常见问题与排查技巧实录6.1 实战问题速查表我把平时遇到过的UPDATE相关典型问题整理成一个速查表排查时可以照着对一下现象可能原因排查方法解决方向UPDATE执行后受影响行数为0新值和旧值相同MySQL优化跳过了写入检查SET字段的当前值业务层不要依赖affected rows判断是否命中UPDATE非常慢即使只改一行WHERE条件字段无索引全表扫描加全表锁EXPLAIN看type字段为WHERE字段建立合适的索引UPDATE报Deadlock多个事务锁顺序不一致或间隙锁互相等待查看SHOW ENGINE INNODB STATUS中的LATEST DETECTED DEADLOCK事务内按固定顺序操作缩小事务范围UPDATE报Lock wait timeout其他事务持有行锁未提交查information_schema.INNODB_TRX定位持锁事务评估是否需要KILL子查询里引用同一张表报错MySQL不允许UPDATE目标表出现在FROM子查询里查看错误码1093多包一层临时表或者改成JOIN写法多表更新报Column ambiguous多个表存在同名字段SET未加表名前缀检查SET子句字段前明确加表别名或表名大批量UPDATE导致主从延迟单条UPDATE生成大量binlog从库重放耗时监控Seconds_Behind_Master改成分批UPDATE降低单批数据量6.2 案例复盘一条慢UPDATE把从库拖垮的全过程一次线上事故让我印象很深。某个后台管理系统里有一个“全量同步”按钮后端代码执行了一条近似的UPDATEUPDATE user_points SET points points 100 WHERE user_id IN (SELECT id FROM temp_users);temp_users表里大约有几百万个iduser_points表本身有上亿行。这条SQL在主库上跑了很久产生了几百万行的binlog从库一路重放延迟飙到十几分钟。前台用户查积分都是走从库的结果查到的全是旧数据投诉就来了。复盘下来问题出在两个层面一是这条UPDATE没有分批一次更新几百万行持锁时间长binlog量大二是temp_users表数据本身就是分批导入的但UPDATE只执行了一次等于把所有压力都集中在了一条SQL上。后来改成按temp_users里的批次字段分批执行UPDATE每批5万行中间sleep 100毫秒主从延迟很快降到了正常水平。这个案例印证了一个观点写UPDATE不能只看“能不能执行成功”还要看它的执行过程对上下游链路造成什么影响。生产环境里SQL的体面和优雅体现在对系统整体性能的尊重上。6.3 防止UPDATE误操作的最后一道防线binlog备份恢复如果真的不小心执行了一条错误的UPDATE数据被改了还能恢复吗答案取决于binlog的配置。MySQL开启binlog后每一条UPDATE都会记录变更前后的日志前提是binlog格式为ROW模式这种模式下的binlog记录了改动前和改动后的完整镜像。出问题时可以用mysqlbinlog工具解析binlog找到出问题的那条SQL对应的日志位置把记录里的前镜像提取出来反向生成恢复SQL再把数据改回去。恢复流程大致分三步先用SHOW BINARY LOGS确认当前有哪些binlog文件再用mysqlbinlog --base64-outputDECODE-ROWS -v binlog.000123解析日志定位到误操作语句附近的事件最后把前镜像生成INSERT或UPDATE语句逐行恢复。但说实话binlog恢复只是补救手段远没有预防来得划算。日常开发中更值得做的是高危操作前先备份、操作前用SELECT确认影响行数、执行后即刻校验数据。数据安全这件事永远是一分预防大于十分补救。7. 从开发到DBA都该记住的UPDATE知识清单这篇内容写到这里该把Update相关的知识点做个收拢了。我不能替你保证线上永远不出事故但按这套思路去写UPDATE大概率能让你的下场比那些“在SELECT上纵横捭阖、在UPDATE上翻车”的兄弟体面不少。我个人在实际操作中特别推荐几种做法一是所有更新类的SQL都统一带WHERE前缀检查无论是手动执行还是代码里拼SQL先跑一遍SELECT再跑UPDATE二是把所有写库操作尽量收敛到事务里事务里不要夹杂外部调用三是对任何超过预期影响行数的变更先想想索引和锁再决定要不要继续。另外一个小技巧当你在Navicat、DataGrip这类客户端里执行UPDATE时尽量开启事务模式关闭autocommit执行完先看数据再手动COMMIT。许多客户端默认每条SQL自动提交一旦执行错就毫无反悔余地。这个习惯我在多次深夜救火之后越来越觉得是保命级别的。如果用一句话来概括UPDATE这一章的内容我会说它是一把手术刀割得准能帮你精准修正数据割得不准就要面对大面积组织的损伤。多花几分钟确认边界比事后花几小时找备份要值得多。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。