mysql并发更新同一行数据怎么办_mysql冲突解决思路

来源:这里教程网 时间:2026-02-28 20:43:34 作者:

UPDATE 语句没加 WHERE 条件导致“覆盖式更新”

这是最常见也最容易被忽略的并发问题:多个事务同时执行

UPDATE table SET status = 'done'
这类无条件更新,后提交的事务会直接覆盖前一个事务的修改,且不报错。MySQL 不会主动检测逻辑冲突,只保证行锁的原子性。

实操建议:

所有
UPDATE
必须带明确的
WHERE
条件,最好基于主键或唯一索引,例如
WHERE id = 123
避免用
WHERE status = 'pending'
这类可能匹配多行、且在并发下条件已失效的写法
如果业务上确实需要“只更新未处理过的记录”,改用
UPDATE ... SET status = 'done' WHERE id = 123 AND status = 'pending'
,然后检查
ROW_COUNT()
是否为 1

乐观锁:用 version 字段 + 条件更新防覆盖

适合读多写少、冲突概率低的场景。核心是把“检查+更新”合并为一条带条件的

UPDATE
,由数据库原子执行。

示例表结构:

CREATE TABLE order_info (
  id BIGINT PRIMARY KEY,
  status VARCHAR(20),
  version INT DEFAULT 0
);

更新逻辑(应用层):

先查出当前
id = 1001
的记录,拿到
version = 5
执行:
UPDATE order_info SET status = 'shipped', version = 6 WHERE id = 1001 AND version = 5
ROW_COUNT() == 0
,说明已被其他事务更新,当前操作失败,需重试或提示用户

注意:

version
字段必须是整数类型,且每次更新都自增;不能用时间戳替代,因为精度和时钟同步问题会导致误判。

悲观锁:SELECT ... FOR UPDATE 要慎用

SELECT ... FOR UPDATE
在事务中加行级写锁,能阻止其他事务修改同一行,但代价是锁持有时间长、容易引发死锁或锁等待超时。

关键限制:

必须在
START TRANSACTION
内使用,且锁持续到事务结束(
COMMIT
ROLLBACK
只对有索引(尤其是主键/唯一索引)的查询生效,否则会升级为表锁 不要在长事务或含远程调用的事务里用它——比如查完就去调用微信 API,这期间锁一直占着 如果业务允许,优先用乐观锁;只有强一致性要求、且冲突频繁时才考虑悲观锁

典型错误写法:

SELECT * FROM user WHERE name = 'alice' FOR UPDATE
—— 若
name
无索引,实际锁整张表。

REPLACE INTO / INSERT ... ON DUPLICATE KEY UPDATE 不等于并发安全

这两个语法常被误认为能解决并发更新,其实它们只适用于“存在则更新、不存在则插入”的场景,且依赖唯一键冲突触发。对已存在的行做重复更新时,它们不会判断业务状态是否合理。

举例:

订单表有唯一索引
(order_no)
,两个事务同时执行:
INSERT ... ON DUPLICATE KEY UPDATE status = 'cancelled'
结果是最后提交者胜出,但没人知道谁该真正取消——这属于业务逻辑冲突,不是数据库能自动解决的 它们也无法防止“先查再更”里的中间态问题(如 A 查到 status=‘pending’,B 同时改为 ‘processing’,A 仍按 pending 更新)

真正要靠的是显式条件判断(乐观锁)或事务隔离级别配合锁机制(如

SELECT ... FOR UPDATE
),而不是依赖冲突触发的语法糖。

并发更新的本质不是“怎么让 SQL 更快”,而是“如何让多个操作在业务意义上互斥”。多数情况下,

WHERE ... AND version = ?
加上应用层重试,比锁表、升隔离级别或引入分布式锁更轻量、更可控。容易被忽略的是:版本字段的维护是否严格、重试逻辑有没有幂等保护、以及
ROW_COUNT()
是否真被检查了。

相关推荐