MySQL并发更新配置表易丢数据,因REPEATABLE READ不防“读-改-写”竞态;应加version校验、唯一索引、行锁或拆分表,并建立发布流程。

会出问题,尤其是没加锁或没用事务控制时,UPDATE 同一行配置项大概率导致丢失更新(lost update)。
为什么并发改同一张配置表容易丢数据
MySQL 默认隔离级别是 REPEATABLE READ,但它不阻止两个事务同时读到旧值、各自计算后写回——这就是典型的“读-改-写”竞态。比如两个服务同时读到 status = 1,都改成 2 再 UPDATE,最终只生效一次。
- 没加
WHERE条件或条件不精确(如用name = 'timeout'但没建唯一索引),可能误改多行 - 用
UPDATE config SET value = ? WHERE key = 'log_level'这种语句,无版本号或时间戳校验,无法感知冲突 - 应用层缓存了配置,数据库改了但缓存没清,造成“已更新却未生效”的假象
推荐的并发安全写法(带校验)
核心思路:让更新本身携带“预期状态”,失败即重试或报错,而不是静默覆盖。
- 加
version字段,每次更新都检查并自增:UPDATE config SET value = 'debug', version = version + 1 WHERE key = 'log_level' AND version = 5;
返回affected_rows == 0就说明被别人抢先改了 - 用
ON DUPLICATE KEY UPDATE(需先建唯一键):INSERT INTO config (key, value, updated_at) VALUES ('retry_times', '3', NOW()) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = NOW(); - 对关键开关类配置(如
is_maintenance_mode),直接用SELECT ... FOR UPDATE显式加行锁(仅限 InnoDB)
配置表设计避坑点
不是所有字段都适合放进一张泛型 config 表。结构松散会放大并发风险,也难加约束。
- 避免用
key/value大宽表存所有配置;应按业务域拆分,比如app_config、payment_config,每张表字段明确、类型固定 -
key字段必须加UNIQUE索引,否则UPDATE ... WHERE key = ?可能锁住多行甚至全表 - 不要用
TEXT存简单字符串,优先用VARCHAR(255);大配置(如 JSON 模板)可单独字段,但读写要分离,避免每次改小配置都加载大字段 - 记录操作人和时间很重要:
updated_by VARCHAR(64)和updated_at DATETIME(3),出问题能快速追溯
最麻烦的不是怎么写 SQL,而是配置变更缺乏发布流程——比如开发直接连线上库 UPDATE,还没通知下游服务 reload。并发问题往往只是表象,背后是治理缺失。










