资讯动态

MySQL UPDATE操作全解析:从基础语法到锁机制与批量更新实战

发布时间:2026/10/13 4:28:43 来源:尧图企业网站定制
做后台开发和数据分析的天天都要跟MySQL打交道。增删改查里UPDATE可能是最容易被轻视却又最容易出事的操作。DELETE好歹有个高危操作的标签提醒你小心SELECT又是高频使用的老朋友唯独UPDATE看起来人畜无害语法简单一行UPDATE t SET field value就算完事。但就这么个简单操作线上偶发的一次全表更新、一次错误的WHERE条件、一次锁竞争超时就能让业务停摆、数据错乱甚至直接把这周的工作成果清零。这篇文章我就把UPDATE从基础语法到进阶用法、从执行原理到典型踩坑场景系统梳理一遍。无论你是刚接触SQL的新手还是写过不少业务代码但没深究过细节的老手都能从里面拿到几招管用的东西。内容包括单表更新、多表关联更新、利用CASE WHEN做批量条件更新以及几个我在真实生产和测试环境里反复踩过的坑和对应的排查思路。1. UPDATE的执行逻辑与基础语法先说最核心的一点UPDATE更新数据改变的不仅仅是表里的数据它还会影响索引、产生binlog日志二进制日志、触发可能的触发器并在事务中占据锁资源。很多新手只盯着值改没改对忽略了这些连带效应后期排查问题时就容易懵。所以我们在说怎么写UPDATE之前先把它到底干了什么想清楚。1.1 一条UPDATE语句的执行旅程当客户端把一条UPDATE语句发给MySQL服务端大概会经历下面几步连接器负责建立连接、获取权限。分析器做词法分析和语法分析确认表名、列名、条件是否合法。优化器决定用哪个索引来定位需要更新的行这直接决定这条语句是快是慢。执行器根据优化器选择的执行计划逐行或批量读取满足条件的记录。存储引擎InnoDB默认先将要更新的行所在的数据页载入内存然后对记录加锁。如果没有在内存中还要先从磁盘读取。执行器对满足条件的行做修改然后写redo log重做日志通常还要配合binlog做两阶段提交来保证崩溃后的恢复能力。如果有触发器、生成列、外键约束也会在这个阶段一并处理。提交事务释放锁。这里最值得展开的是第5步的加锁逻辑。因为InnoDB默认的隔离级别是RR可重复读UPDATE通常会加行锁但如果WHERE条件里用的列没有索引或者优化器判断走全表扫描更划算那InnoDB为了不让别的连接插入或修改数据造成冲突可能会锁住更多行极端情况下就是锁全表。这也是为什么很多人在生产环境执行一个看上去没问题的UPDATE结果把并发业务卡到爆的原因。1.2 UPDATE基础语法结构先看最朴素的单表更新语法UPDATE table_name SET column1 value1, column2 value2, ... WHERE condition;UPDATE关键字后面跟要更新的表名。SET子句列出要修改的列和新的值。WHERE条件用于限定哪些行需要被更新。注意两点列名后面可以跟单表/多表限定比如t1.name t2.namevalue可以是常量、表达式、函数也可以是子查询的结果。例如-- 1) 把价格统一上调10% UPDATE products SET price price * 1.1 WHERE category_id 5;-- 2) 把订单表的最后修改时间更新为当前时间 UPDATE orders SET updated_at NOW() WHERE order_id 1024;-- 3) 把某个用户的其他默认地址全部置为非默认 UPDATE user_address SET is_default 0 WHERE user_id 888 AND is_default 1;这三条在写法上都很基础但第1条和第3条有一个非常容易被忽略的细节执行UPDATE时MySQL用的是当前行修改前的旧值来计算表达式。也就是说price price * 1.1在每一行上等号右边的price都是这一行更新前的原始值不会出现上一行改完了下一行用的是被改过的新值这种连锁污染的情况。这一点在批量操作时尤其是好消息因为MySQL的UPDATE不会像某些编程语言里的循环更新那样产生脏读式的累积误差。1.3 WHERE条件缺失的严重后果这可能是整个UPDATE话题里最老生常谈却依然每天都在发生的悲剧-- 危险示例全表更新 UPDATE users SET is_active 1;如果users表有几十万行、几百万行这语句会把所有行的is_active字段全部改成1。如果刚好是想把一部分人的账号停用结果把所有人的账号都激活了那就等着被业务方问候吧。更头疼的是在没有开启安全模式的情况下MySQL默认并不拦截这种没有WHERE条件的UPDATE。它只会简单回报Query OK, 500000 rows affected仿佛一切都理所当然。所以我在自己的开发环境里几乎必开sql_safe_updates它就是一道护栏如果没有WHERE条件或者WHERE条件里没有使用索引列UPDATE和DELETE会直接被拒绝执行。线上环境如果基于某些原因不方便开至少要在流程上强制要求所有UPDATE语句必须经过review且必须带有明确的WHERE条件。这背后其实还有一个更深层的问题当你执行全表更新时不仅是数据全变了整个表的行锁会陆续被持有大事务持续时间变长可能拖垮从库的复制延迟还可能让后续的DDL操作排队等锁。所以一条不带WHERE的UPDATE伤的不只是这一张表的数据是整个MySQL集群的稳定。2. WHERE条件的设计索引、函数与性能隐患UPDATE能不能快和它怎么定位目标行关系重大。SET后面改成什么值当然重要但决定这条SQL生死的是WHERE。如果WHERE命中索引MySQL只需要锁住那几行如果WHERE没走索引那可能就需要扫描大量记录锁的范围也跟着变大。2.1 WHERE条件命中索引的重要性假设有一张订单表CREATE TABLE orders ( order_id BIGINT PRIMARY KEY, user_id INT NOT NULL, status TINYINT NOT NULL DEFAULT 0, total_amount DECIMAL(10,2) NOT NULL, created_at DATETIME NOT NULL, INDEX idx_user_id (user_id) ) ENGINEInnoDB;如果我们要把某个用户的所有待支付订单改成已取消UPDATE orders SET status 3 WHERE user_id 20240 AND status 0;user_id有索引优化器可以先快速定位到20240这个用户的全部订单再做status过滤。这个过程通常很快锁住的也只是这一小部分行。但如果把条件换成WHERE status 0而status列上根本没有索引优化器就得扫全表逐行判断状态。数据量一大后果就是慢查询、锁范围扩大甚至长时间抢占CPU和IO资源。我见过不少生产事故就是因为临时加一个条件改成了未索引列紧接着线上接口大面积超时。所以我在每次写UPDATE之前习惯先EXPLAIN一下确认key列不是空type不是ALL心里才踏实。2.2 函数包裹索引列导致索引失效一个极易踩的坑对索引列套函数。比如UPDATE orders SET status 2 WHERE DATE(created_at) 2024-12-01;created_at明明是索引列但一旦被DATE函数包裹索引就废了。因为它需要算出每一行的DATE(created_at)之后才能比较这基本等价于全表扫描。正确写法是使用范围条件UPDATE orders SET status 2 WHERE created_at 2024-12-01 00:00:00 AND created_at 2024-12-02 00:00:00;同理如果某个条件列是字符串类型而你在比对时忘了写引号UPDATE user_info SET nickname 新昵称 WHERE mobile 13800138000; -- mobile是varchar这里被隐式转成数字这会导致索引失效甚至引发误更新。因为MySQL在比较时会先把mobile转成数字13800138000可能匹配到多个带不同前缀的号码结果改错的就不止一行了。2.3 LIMIT与ORDER BY的辅助价值有些场景不需要把所有满足条件的行一次更新完。比如要分批处理几百万行的数据你可以用UPDATE big_table SET processed 1 WHERE processed 0 ORDER BY id LIMIT 1000;加上ORDER BY可以让每次更新都从最早的一批开始配合ID自增实现类似水位线的推进。这样单条SQL执行时间短、锁持有时间短不会长时间阻塞其他业务。加上LIMIT的另一个作用是保险丝即使WHERE条件写得不理想最多也只影响LIMIT指定的行数不会一下把全表都拖下水。LIMIT在DELETE里很常见在UPDATE里反而被很多人忽视。实际上对于大表做数据订正分批UPDATELIMIT是一个屡试不爽的经典模式。3. 多表更新与JOIN实战技巧单表更新解决的是一张表内部改字段的需求但业务中大量场景是一张表的数据要参考另一张表的值来更新。3.1 MySQL多表UPDATE的两种写法MySQL支持两种实用的多表更新写法。第一种是直接在UPDATE语句后面跟多张表在WHERE里写关联条件UPDATE orders o JOIN users u ON o.user_id u.id SET o.total_amount o.total_amount * 0.95 WHERE u.vip_level 3;这个写法把orders表要更新哪些行和users表提供什么条件或取值放在同一条SQL里逻辑非常清晰先把满足VIP3条件的用户对应的订单找出来再对这些订单打95折。第二种是使用子查询来更新UPDATE orders o SET o.total_amount o.total_amount * 0.95 WHERE o.user_id IN ( SELECT id FROM users WHERE vip_level 3 );两种写法的取舍点在于JOIN写法更灵活能直接在SET阶段引用另一张表的列比如SET o.remark u.name。子查询写法更容易读懂适合只要用另一张表做条件过滤不需要取它的字段值的场景。性能上MySQL优化器通常能把简单的IN子查询改写成半连接semi-join不一定比JOIN慢。但遇到复杂子查询时建议还是EXPLAIN看一下。3.2 JOIN更新时要注意的三件事第一JOIN更新的WHERE条件如果不够精确容易造成宁多勿少式误更新。比如UPDATE orders o LEFT JOIN users u ON o.user_id u.id SET o.total_amount 0 WHERE u.vip_level 3;LEFT JOIN会让users表不匹配的行保留下来WHERE里再用u.vip_level 3过滤其实等价于INNER JOIN但问题是如果u.vip_level为NULL某些版本下WHERE条件对NULL的处理容易让人迷糊。更安全的是直接用INNER JOIN明确表达只更新匹配上的行。第二多表更新的SET赋值顺序和单表一致但要注意如果JOIN产生一对多匹配比如users表和orders表是一对多而你在SET里用到了与orders无关的users字段一条user可能对应多条order更新结果不会重复累加因为每条order独立更新。但如果两张表的关联关系本身是一对多而你本意是想每个user只更新一次那么用JOIN更新就有风险。比如UPDATE users u JOIN ( SELECT user_id, COUNT(*) cnt FROM orders GROUP BY user_id ) tmp ON u.id tmp.user_id SET u.order_cnt tmp.cnt;这是典型的先聚合、再关联场景子查询里已经把每个user的订单数算好users表和子查询结果是一对一安全。相反如果直接用orders表和users表JOIN就会因为一个user对应多行orders而对同一个user更新多次最终结果难以预料。第三多表UPDATE对权限和binlog也有影响。生产环境如果有基于语句复制的从库多表UPDATE可能会因为主从执行计划不同而产生数据不一致。解决思路是把binlog_format设为ROW或者尽量减少这类语句的使用频率尤其是大事务里的多表更新。3.3 利用CASE WHEN实现一行一值的批量更新业务中更常见的需求是同一张表里满足不同条件的行要更新成不同的值。新手最容易想到的是写多条UPDATE比如UPDATE user_level SET level_name 青铜 WHERE level_id 1; UPDATE user_level SET level_name 白银 WHERE level_id 2; UPDATE user_level SET level_name 黄金 WHERE level_id 3;在数据量小、事务要求不高时这没什么问题。但如果你想把所有更新放进一个事务里原子执行并且减少SQL往返次数就可以用一个UPDATE配合CASE WHEN完成UPDATE user_level SET level_name CASE level_id WHEN 1 THEN 青铜 WHEN 2 THEN 白银 WHEN 3 THEN 黄金 ELSE level_name END WHERE level_id IN (1, 2, 3);这里有一个关键技巧ELSE level_name。这意味着不在更新范围内的行保持原值不变。如果漏掉ELSE所有符合WHERE条件的行里没有被CASE匹配到的都会被更新成NULL这是新手写CASE WHEN更新最容易翻车的点。同理如果你希望根据某个字段的不同区间来更新另一列也可以用UPDATE users SET level CASE WHEN total_amount 10000 THEN VIP4 WHEN total_amount 5000 THEN VIP3 WHEN total_amount 1000 THEN VIP2 ELSE VIP1 END;这种写法的实际收益不止是减少了SQL条数它还能确保这一批更新在同一个语句内完成要么全部成功要么全部回滚不存在改到一半某条失败导致部分更新的尴尬。尤其是配合事务使用时单条UPDATE天然就是原子的。4. UPDATE与事务、锁、binlog的联动关系不知道你有没有遇到过这样的怪事明明只更新了一行数据业务却提示锁等待超时。这时候如果不了解UPDATE和锁、事务的联动关系排查起来会非常痛苦。4.1 行锁、间隙锁与幻读MySQL默认的存储引擎InnoDB默认隔离级别是可重复读Repeatable Read。在RR级别下执行UPDATE不仅会锁住命中的记录行还可能在索引区间上加上间隙锁Gap Lock防止其他事务在这个区间插入新数据。这个机制是为了解决当前读下的幻读问题。什么情况下容易触发间隙锁典型的就是WHERE条件是范围查询。比如UPDATE orders SET status 1 WHERE created_at 2024-11-01 AND created_at 2024-11-30;这条SQL不仅会锁住11月已经存在的所有订单行还可能锁住11月这个时间范围内的空白间隙意味着其他事务想往这个时间段插入新订单也可能被阻塞住。如果这个时间段特别长比如查的是2023年整年那么锁的范围就会非常大直接阻塞业务上的正常插入操作。我遇到过一次典型的线上卡顿就是因为某后台功能执行了按月更新状态的UPDATE恰好那一个月还是未来时间段间隙锁把新订单插入全部挡住了。后来我们的解决方案是把这种批量更新改成用主键ID精确匹配或者通过任务调度在低峰期执行并且把WHERE的过滤条件设计成只覆盖极少数据量的形状减少间隙锁的触碰面积。4.2 UPDATE执行时间与锁持有时长锁持有时间不等于SQL执行时间。在一个事务里如果是先UPDATE再查询、再UPDATE另一张表事务不COMMIT锁就一直不释放。很多SQL优化后还是很慢的场景其实问题不在SQL本身而是事务迟迟不提交或是在事务里做了一些与本次更新无关的慢查询把整个事务的锁持有时间拉长了。所以一个非常实用的小建议事务尽量短。UPDATE之后如果能立即COMMIT就绝不拖延。尤其不要在UPDATE和COMMIT之间去调用外部HTTP接口、做复杂的批量计算或者等待用户输入。这些都不是数据库该管的耗时一旦掺和进事务里就把数据库的锁资源白白锁在那里。4.3 binlog与复制环境下的UPDATE从MySQL 5.7开始binlog的默认格式可能是ROW也可能是MIXED取决于安装和配置。很多人在做主从复制排查时发现从库数据和应用端预期不一致根因往往出在UPDATE语句上。因为基于STATEMENT格式时从库会重新执行一遍相同的SQL如果主从库的数据、索引、优化器选择有差异就可能产生不同的更新结果。而基于ROW格式时binlog记录的是每一行修改前后的值从库直接应用这些行级别的变更一致性要强得多。因此如果你的业务依赖主从复制优先确认binlog_format是ROW。这通常也是云数据库默认推荐的配置。用了ROW之后大量更新虽然在binlog里产生的日志量会变大磁盘占用会涨但换来的是复制的高可靠性。另外还要注意在ROW格式下大事务的binlog量会显著增长例如一次更新几十万行日志文件会非常大同步到从库也要花更长时间。从库延迟过高时除了检查主库的长事务也要看看binlog是不是在同一时间产生了巨量变更。4.4 慢UPDATE与长时间未提交的排查当线上出现UPDATE卡住我一般会通过下面几条命令快速定位-- 查看正在执行的SQL和状态 SHOW FULL PROCESSLIST; -- 查看当前有哪些事务没提交 SELECT * FROM information_schema.innodb_trx\G -- 查看锁等待关系 SELECT * FROM sys.innodb_lock_waits; -- 查看锁等待超时时间设置 SHOW VARIABLES LIKE innodb_lock_wait_timeout;SHOW FULL PROCESSLIST能看到每个连接的当前SQL如果个别连接长时间处于Waiting for table metadata lock或者Updating状态要重点排查。innodb_trx表能查每个未结束事务的开始时间和执行过的SQL特别适合揪出开了事务忘了提交的罪魁祸首。我遇到过一个坑某开发在代码里手动开了事务执行UPDATE后因为后面的业务逻辑抛出异常事务一直没回滚也没提交数据库连接被占用锁持续持有。后来其他用户对这个表的任何UPDATE和DELETE全部阻塞。排查时看到该连接的事务已经挂了半小时问题根子不在SQL而在业务代码的事务边界管理。这也是为什么我强烈建议除非有特殊的强一致性要求否则能用单条SQL表达的事情就尽量别手工包一个大事务。5. UPDATE实战案例从表结构设计到语句落地理论说了不少还是落到具体可复制的例子更有价值。我整理一个完整的实战场景从表结构到UPDATE语句再到验证方法一条龙说清楚。5.1 模拟业务场景假设有一个电商系统需要实现这样的需求用户表里记录了用户的会员等级。订单表里有订单金额、订单状态。每月1号要把上个月所有VIP用户的订单金额在原基础上打95折。同时把订单状态的待支付改成营销活动价已调整。表结构如下CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, vip_level TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL ) ENGINEInnoDB; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL, INDEX idx_user_id (user_id), INDEX idx_created_at (created_at), CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id) ) ENGINEInnoDB;5.2 用JOIN UPDATE完成核心需求第一步找出上个月的VIP用户的订单并进行折扣更新UPDATE orders o JOIN users u ON o.user_id u.id SET o.total_amount o.total_amount * 0.95, o.status 1 WHERE u.vip_level 3 AND o.status 0 AND o.created_at 2024-12-01 00:00:00 AND o.created_at 2025-01-01 00:00:00;这里有几个细节值得展开WHERE里先过滤订单表的created_at再用u.vip_level 3过滤用户索引利用顺序和优化器决策相关但EXPLAIN通常能帮我们确认是否走orders表主键或created_at索引。SET阶段同时改两个字段减少了一轮UPDATE调用。关联关系是orders表的user_id对应users表的id因为user_id上有索引JOIN性能通常没问题。5.3 正确使用LIMIT做批量收尾如果上个月的订单量非常大一次UPDATE可能执行几秒甚至几十秒这在低峰期可以接受但如果在业务高峰期强行执行会引发不必要的锁竞争。优化方案就是分批执行UPDATE orders o JOIN users u ON o.user_id u.id SET o.total_amount o.total_amount * 0.95, o.status 1 WHERE u.vip_level 3 AND o.status 0 AND o.created_at 2024-12-01 00:00:00 AND o.created_at 2025-01-01 00:00:00 ORDER BY o.id LIMIT 5000;每次只更新5000行执行完后提交然后重复执行这条SQL直到影响行数为0。这种方式特别适合数据订正和异步任务。为了更自动化可以写一个存储过程或者应用层循环来完成。需要注意的一个陷阱LIMITJOIN时如果多表JOIN产生了重复行的匹配LIMIT的语义是限制最终被更新的行数而不仅仅是限制左表读取的行数。MySQL官方文档也提示过多表UPDATE里使用ORDER BY和LIMIT时可能会因为执行计划不同而出现难以预期的行为。所以更稳妥的做法是先用子查询把要更新的主键ID列表算出来再通过主键批量更新。5.4 基于主键ID分批更新的推荐方案-- 第一步查出一批需要更新的订单ID SELECT id FROM orders o JOIN users u ON o.user_id u.id WHERE u.vip_level 3 AND o.status 0 AND o.created_at 2024-12-01 00:00:00 AND o.created_at 2025-01-01 00:00:00 ORDER BY o.id LIMIT 5000; -- 假设取出的ID列表是 [1001, 1002, ..., 6000] -- 第二步以主键为条件更新锁定范围最小 UPDATE orders SET total_amount total_amount * 0.95, status 1 WHERE id BETWEEN 1001 AND 6000 AND status 0;这样做的好处是UPDATE语句里没有了JOIN锁和扫描的开销绝大多数发生在SELECT阶段而SELECT不会持有长时间行锁真正的更新操作在WHERE条件为显式ID范围时InnoDB能精准定位锁范围很小速度极快。对于线上大表这是最稳妥的批量更新姿势。5.5 UPDATE之后如何验证写完UPDATE别急着收工。尤其涉及金额、状态这类重要数据一定要做前后校验。比较常用的方式-- 更新前快照 SELECT COUNT(*) AS cnt_before, SUM(total_amount) AS amount_before FROM orders WHERE created_at 2024-12-01 00:00:00 AND created_at 2025-01-01 00:00:00 AND status 0 AND user_id IN (SELECT id FROM users WHERE vip_level 3); -- 更新后快照 SELECT COUNT(*) AS cnt_after, SUM(total_amount) AS amount_after, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS status_changed_cnt FROM orders WHERE created_at 2024-12-01 00:00:00 AND created_at 2025-01-01 00:00:00;比较cnt_before和status_changed_cnt是否一致amount_after是否约等于amount_before乘以0.95就能判断更新是否符合预期。金额有小数时注意DECIMAL类型的舍入规则不建议用FLOAT存金额否则对比时经常出现0.01的偏差。6. UPDATE常见问题与排查技巧速查最后把这几年遇到的高频UPDATE问题整理成一张速查表平时遇到类似情况可以对照检查。问题现象可能原因排查思路UPDATE执行很久不结束WHERE没走索引全表扫描EXPLAIN看type补充索引UPDATE后其他SQL锁等待超时事务未及时提交锁持有过长SHOW FULL PROCESSLIST查innodb_trxUPDATE报错Data too long写入值超出列定义长度查看字段类型改用TEXT或扩大长度UPDATE报错Duplicate entry更新后的值与唯一键冲突先查询是否已有相同值修改更新策略WHERE条件生效但影响行数不对JOIN产生一对多匹配用子查询或者先聚合再关联更新后从库数据和主库不一致binlog_format为STATEMENT改为ROW修复从库数据没写WHERE条件的UPDATE全表被改人为失误开启sql_safe_updates增加审批流程使用函数后索引失效WHERE里对索引列套了函数改成范围条件或等值条件批量更新大表导致主从延迟单条UPDATE影响行数过大分页/分批更新控制在数千行以内UPDATE不报错但数据没变新值和原值相同MySQL跳过了写入检查影响行数是否为0对比前后值同一行并发更新互相覆盖缺少乐观锁/版本号机制加version字段用CAS方式更新事务回滚后自增ID不连续更新失败导致自增消耗这是InnoDB正常现象无需处理6.1 sql_safe_updates这个保命开关MySQL客户端参数sql_safe_updates可能很多人没注意过但它是真实能救命的东西。开启后所有没有WHERE条件或WHERE里没有使用索引列的UPDATE/DELETE都会被拒绝执行。设置方式SET sql_safe_updates 1;如果你正在执行大规模数据订正确实想要更新全表那就先想办法加一个合理的WHERE条件或者干脆先备份再操作。生产环境建议至少在所有开发测试环境开启线上可通过管理平台统一下发参数。一条误操作直接拒绝的护栏比任何下次注意都管用。6.2 关于UPDATE影响行数的误读很多人会把Query OK, 0 rows affected理解成更新失败。其实0 rows affected很可能只是因为新值和旧值完全相同InnoDB出于性能考虑跳过了物理写入。这个行为不是错误但如果你期望更新生效却发现0行要重点检查WHERE条件是否写错或者SET的值是否本来就是当前值。另外一条UPDATE更新很多行时如果某一行违反约束导致整体失败InnoDB会回滚整条语句之前的效果。所以不要指望部分成功——要么全部成功要么全部回滚这也是单条UPDATE最强的原子性保证。6.3 NOW()函数在批量更新中的陷阱使用NOW()给时间字段赋值时如果一次UPDATE影响多行所有行拿到的是同一个执行时刻这在大多数业务逻辑里是合理的。但如果有人在循环里多次调用UPDATE并检查时间排序可能因为相同时间戳导致排序不稳定。更稳妥的做法是使用应用层精确时间或者在UPDATE时加上自增/随机微秒偏移。不过大多数场景不必过度设计。7. 个人经验分享我自己在写UPDATE这条语句时已经形成了肌肉记忆般的几个习惯。第一任何UPDATE写完后会在提交前重新审视一遍WHERE条件尤其检查有没有漏掉AND is_deleted 0、AND status 0之类的业务软删过滤。第二涉及到金额、库存、积分这类数值更新前和更新后各写一条SELECT进行快照对比。第三更新影响行数超过预期边界时先回滚事务查清楚影响范围再重来。还有一个小技巧如果是要把线上一个表的数据从A状态改成B状态建议先把它扔到测试库的同构表里跑一遍看看影响行数和自己估算的是否一致。很多时候数据表象和真实分布差距很大你以为只有几条的数据实际可能命中几万行。测试库跑一遍比自己选SELECT玩心跳要安全得多。UPDATE这个操作表面上是数据库里最基础的语法但能把它用得稳、用得准、用得高效是需要一点实战沉淀的。希望这篇内容能帮你减少几个线上事故的潜在风险。如果后续在MySQL执行计划、锁机制或者数据订正方案上还想聊得更细那就是下一篇文章的题材了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑