会出问题,尤其是没加锁或没用事务控制时,
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。并发问题往往只是表象,背后是治理缺失。
