已检查unique_key仍出现重复行?SQL与PHP代码问题求助
核心原因分析
1. 数据库未对unique_key设置唯一约束
这是最关键的问题:你的代码逻辑依赖“存在则更新”,但数据库本身没有强制unique_key的唯一性,就算代码判断出错,数据库也会允许插入重复值。而且高并发场景下,两个请求可能同时通过“不存在”的检查,进而都执行插入操作,直接产生重复行。
2. 竞态条件(并发场景)
当多个请求同时处理同一个unique_key时,请求A刚执行完sql_check_row判断不存在,还没插入;请求B也执行sql_check_row,同样判断不存在,最后两个请求都执行插入,导致重复。
3. SQL字符串拼接的安全问题(可能导致判断失效)
你的辅助函数直接拼接SQL语句,比如sql_check_row里的WHERE unique_key = 'xxx',如果unique_key包含单引号、反斜杠等特殊字符,会导致SQL语法错误,mysqli_num_rows返回0,代码误以为该行不存在,进而执行插入操作,产生重复。
4. 数据库连接关闭函数混用API
sql_connect用的是mysqli_connect(mysqli扩展),但sql_close却用mysql_close(mysql扩展,已废弃),这会导致连接无法正确关闭,虽然不是直接导致重复,但会引发其他潜在问题。
修复步骤
1. 给数据库表添加唯一约束
直接在数据库层面强制unique_key的唯一性,这是最可靠的保障:
ALTER TABLE 你的表名 ADD UNIQUE INDEX idx_unique_key (unique_key);
之后如果再尝试插入重复的unique_key,数据库会直接报错,不会产生重复行。
2. 使用INSERT ... ON DUPLICATE KEY UPDATE替代“先查后改”
这是解决竞态条件的最优方案,把“检查+插入/更新”合并成一条原子SQL语句,避免并发问题:
修改send_score.php的逻辑,去掉sql_check_row的判断,直接执行以下逻辑:
// 替换原来的if-else逻辑 $stmt = mysqli_prepare($con, "INSERT INTO {$tabla} (unique_key, nick, sc) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE nick = ?, sc = ?"); mysqli_stmt_bind_param($stmt, "ssiss", $unique_key, $nick, $puntos, $nick, $puntos); $result = mysqli_stmt_execute($stmt); if ($result) { echo mysqli_affected_rows($con) > 1 ? "OK UPDATE" : "OK INSERT"; } else { echo "操作失败: " . mysqli_error($con); } mysqli_stmt_close($stmt);
3. 修复辅助函数的SQL注入问题(改用预处理语句)
所有SQL操作都要使用预处理语句,避免字符串拼接带来的安全问题和语法错误,比如修改sql_check_row:
function sql_check_row($con,$table,$key,$value) { $stmt = mysqli_prepare($con, "SELECT 1 FROM {$table} WHERE {$key} = ?"); mysqli_stmt_bind_param($stmt, "s", $value); mysqli_stmt_execute($stmt); mysqli_stmt_store_result($stmt); $exists = mysqli_stmt_num_rows($stmt) > 0; mysqli_stmt_close($stmt); return $exists; }
同理,sql_update_row和sql_insert_row都要改成预处理语句的形式。
4. 修复数据库连接关闭函数
把sql_close改成mysqli的关闭函数:
function sql_close($con) { return mysqli_close($con); }
内容的提问来源于stack exchange,提问作者Juan Manuel Almahano

