PHP MySQL实现ID重复时累加更新quantity列的方法
PHP操作MySQL实现ID重复时累加quantity字段方案
核心实现原理
直接使用MySQL原生提供的INSERT ... ON DUPLICATE KEY UPDATE原子语法实现,不需要提前查询记录是否存在,天然避免高并发下的竞态问题,执行逻辑为:
- 尝试执行插入操作
- 如果插入时碰到主键/唯一索引冲突(即待插入ID已存在的情况),则执行后续定义的更新操作
- 不存在冲突则正常插入新记录
前置要求
必须给id字段设置主键约束或者唯一索引,否则MySQL无法识别ID重复的冲突,会直接插入重复ID的脏数据。
代码示例(PDO写法,PHP官方推荐的数据库操作方式)
<?php // 数据库连接配置 $host = '127.0.0.1'; $dbname = '你的业务库名'; $dbUser = '你的数据库账号'; $dbPwd = '你的数据库密码'; try { // 初始化连接,强制utf8mb4字符集 $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $dbUser, $dbPwd); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 业务侧待写入数据,实际场景替换成你的业务取值逻辑即可 $rowData = [ 'id' => 1024, // 待写入的记录ID 'quantity' => 5, // 本次需要新增/累加的数量值 'goods_name' => '机械键盘' // 其他业务字段示例 ]; // 核心SQL:冲突时仅累加quantity字段,不修改其他字段 $sql = "INSERT INTO goods_stock (id, quantity, goods_name) VALUES (:id, :quantity, :goods_name) ON DUPLICATE KEY UPDATE quantity = quantity + :incr_quantity"; $stmt = $pdo->prepare($sql); // 绑定参数,防SQL注入 $stmt->bindValue(':id', $rowData['id'], PDO::PARAM_INT); $stmt->bindValue(':quantity', $rowData['quantity'], PDO::PARAM_INT); $stmt->bindValue(':goods_name', $rowData['goods_name'], PDO::PARAM_STR); // 累加值单独绑定,兼容不同PDO驱动不支持同参数多次复用的问题 $stmt->bindValue(':incr_quantity', $rowData['quantity'], PDO::PARAM_INT); $stmt->execute(); // 可选:根据影响行数判断操作类型 $affectRows = $stmt->rowCount(); if ($affectRows === 1) { // 无冲突,新记录插入完成 echo "新记录插入成功"; } elseif ($affectRows === 2) { // 触发冲突,执行了更新累加 echo "ID已存在,库存数量累加完成"; } } catch (PDOException $e) { die("数据库操作出错:" . $e->getMessage()); }
关键注意事项
- 禁止用「先SELECT查询ID是否存在,再走插入/更新分支」的写法,高并发场景下两个请求同时查到ID不存在,都会走插入逻辑,会导致数量计算错误、主键冲突报错等问题,
INSERT ... ON DUPLICATE KEY UPDATE是数据库层面原子操作,不存在这个问题。 - 禁止用
REPLACE INTO实现需求,该语法的逻辑是检测到冲突后先删除旧记录、再插入新记录,会清空其他字段的原有值,还会导致自增ID异常跳涨,完全不适合累加场景。 - 如果需要在ID冲突时同步更新其他字段,直接在
ON DUPLICATE KEY UPDATE子句后追加即可,比如要更新最后修改时间可以加update_time = NOW(),不需要更新的字段不要写到该子句中,会保留原有值。 - 如果项目用的是MySQLi扩展,核心SQL语句完全不需要修改,只需要把PDO的绑定、执行逻辑换成MySQLi对应预处理写法即可。
内容的提问来源于stack exchange,提问作者Jones Moura
相关产品推荐
相关产品推荐

