
本文详解如何在商品出库操作前校验库存余量,避免因超卖导致库存变为负数,并通过php+mysql实现“库存不足”实时提示与事务安全控制。
本文详解如何在商品出库操作前校验库存余量,避免因超卖导致库存变为负数,并通过php+mysql实现“库存不足”实时提示与事务安全控制。
在电商、仓储或进销存系统中,用户提交出库(如销售、领用)请求时,若未严格校验当前库存,极易引发库存值变为负数(如 -1),造成数据失真和业务逻辑错误。核心问题往往不在数据库触发器本身,而在于应用层缺乏前置校验——正如示例代码所示:变量名拼写错误($tokavailable 误写为 $stokavailable)、逻辑条件颠倒(应为“采购量 ≥ 可用库存”才判定缺货),且未启用数据库事务保障一致性。
以下为优化后的完整解决方案,包含三重防护:
✅ 1. 修复基础逻辑错误
-
变量名统一:从查询结果中正确提取
stokk字段值,赋给$stok_available(推荐语义化命名); -
条件逻辑修正:当用户请求出库数量
$stokk大于等于 当前可用库存时,即判定为“缺货”,立即中断流程并返回提示。
✅ 2. 引入数据库事务确保原子性
单纯先查再插存在竞态风险(如并发请求同时通过校验)。必须使用 START TRANSACTION + ROLLBACK/COMMIT,并在插入前对库存行加锁(SELECT ... FOR UPDATE):
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
date_default_timezone_set("Asia/Jakarta");
$timestamp = date('H:i:s');
$tanggal = date('Y-m-d');
$response = [];
// 获取并过滤输入(务必防注入!)
$id_barang = mysqli_real_escape_string($con, $_POST['id_barang'] ?? '');
$nm_barang = mysqli_real_escape_string($con, $_POST['nm_barang'] ?? '');
$stokk = (int)($_POST['stokk'] ?? 0);
$nm_pegawai = mysqli_real_escape_string($con, $_POST['nm_pegawai'] ?? '');
if (empty($id_barang) || $stokk <= 0) {
$response["value"] = 0;
$response["message"] = "Invalid input: item ID or quantity missing";
echo json_encode($response);
exit;
}
require_once('dbConnect.php');
// 开启事务 + 行级锁(防止并发超卖)
mysqli_begin_transaction($con);
try {
// 查询当前库存并锁定该记录
$sql_check = "SELECT stokk FROM stok WHERE id_barang = ? FOR UPDATE";
$stmt = mysqli_prepare($con, $sql_check);
mysqli_stmt_bind_param($stmt, "s", $id_barang);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
if (!$row = mysqli_fetch_assoc($result)) {
throw new Exception("Item not found in inventory");
}
$stok_available = (int)$row['stokk'];
// ✅ 关键校验:请求出库量 > 当前库存 → 缺货
if ($stokk > $stok_available) {
throw new Exception("Items purchased are out of stock");
}
// 执行出库记录插入
$sql_insert = "INSERT INTO keluar (id_barang, nm_barang, nm_pegawai, stokk, waktu, tanggal)
VALUES (?, ?, ?, ?, ?, ?)";
$stmt = mysqli_prepare($con, $sql_insert);
mysqli_stmt_bind_param($stmt, "sssis", $id_barang, $nm_barang, $nm_pegawai, $stokk, $timestamp, $tanggal);
if (!mysqli_stmt_execute($stmt)) {
throw new Exception("Failed to record outgoing transaction");
}
// ✅ 更新库存表(扣减)
$sql_update = "UPDATE stok SET stokk = stokk - ? WHERE id_barang = ?";
$stmt = mysqli_prepare($con, $sql_update);
mysqli_stmt_bind_param($stmt, "is", $stokk, $id_barang);
if (!mysqli_stmt_execute($stmt)) {
throw new Exception("Failed to update stock");
}
mysqli_commit($con);
$response["value"] = 1;
$response["message"] = "Successful buying";
} catch (Exception $e) {
mysqli_rollback($con);
$response["value"] = 0;
$response["message"] = $e->getMessage();
} finally {
mysqli_close($con);
}
echo json_encode($response);
}⚠️ 重要注意事项
-
永远不要拼接SQL:示例中已全面改用预处理语句(
mysqli_prepare),彻底杜绝SQL注入; -
输入强校验:对
id_barang做非空检查,对stokk强转整型并校验正数; -
锁粒度精准:
SELECT ... FOR UPDATE仅锁定匹配的库存行,不影响其他商品操作; - 错误信息分级:面向用户仅返回友好提示(如“库存不足”),敏感错误(如SQL失败)不应暴露细节;
-
触发器非必需:本方案在应用层完成校验与更新,更可控、易调试;若仍需数据库层兜底,可补充
BEFORE INSERT ON keluar触发器做二次校验。
通过以上重构,系统不仅能准确拦截超量出库请求,还能在高并发场景下保障库存数据的绝对一致性——这才是生产环境应有的健壮设计。
立即学习“PHP免费学习笔记(深入)”;



















