本文目录导读:

在PHP项目中解决重复数据插入,主要有以下几种常用方案,从简单到复杂:
数据库唯一约束(最推荐)
在数据库层面设置唯一索引,这是最根本的解决方案:
-- MySQL 示例 ALTER TABLE users ADD UNIQUE INDEX idx_email (email); -- 或联合唯一 ALTER TABLE orders ADD UNIQUE INDEX idx_user_product (user_id, product_id);
PHP代码中使用 INSERT IGNORE 或 ON DUPLICATE KEY UPDATE:
// 方式1: INSERT IGNORE - 忽略重复
$sql = "INSERT IGNORE INTO users (email, name) VALUES (?, ?)";
$stmt = $pdo->prepare($sql);
$stmt->execute(['test@example.com', '张三']);
// 方式2: ON DUPLICATE KEY UPDATE - 重复时更新
$sql = "INSERT INTO users (email, name, login_count)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE login_count = login_count + 1";
$stmt = $pdo->prepare($sql);
$stmt->execute(['test@example.com', '张三']);
先查询后插入
适用于简单场景,但存在并发问题:
function insertIfNotExists($pdo, $email, $name) {
// 先检查是否存在
$check = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$check->execute([$email]);
if ($check->rowCount() > 0) {
return ['success' => false, 'message' => '数据已存在'];
}
// 不存在则插入
$insert = $pdo->prepare("INSERT INTO users (email, name) VALUES (?, ?)");
$insert->execute([$email, $name]);
return ['success' => true, 'id' => $pdo->lastInsertId()];
}
使用 Redis 缓存/锁(高并发场景)
对于高并发场景,使用 Redis 实现分布式锁:
function insertWithRedisLock($pdo, $redis, $email, $name) {
$lockKey = "user_lock:" . md5($email);
$lockValue = uniqid('', true);
// 尝试获取锁(10秒超时)
if (!$redis->set($lockKey, $lockValue, ['NX', 'EX' => 10])) {
return ['success' => false, 'message' => '操作频繁'];
}
try {
// 再次检查数据库
$check = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$check->execute([$email]);
if ($check->rowCount() === 0) {
$insert = $pdo->prepare("INSERT INTO users (email, name) VALUES (?, ?)");
$insert->execute([$email, $name]);
return ['success' => true, 'id' => $pdo->lastInsertId()];
}
return ['success' => false, 'message' => '数据已存在'];
} finally {
// 释放锁(只释放自己的锁)
$script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
$redis->eval($script, [$lockKey, $lockValue], 1);
}
}
使用批量插入避免重复
function batchInsertUnique($pdo, $data, $batchSize = 100) {
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare("INSERT IGNORE INTO users (email, name) VALUES (?, ?)");
$inserted = 0;
foreach (array_chunk($data, $batchSize) as $batch) {
foreach ($batch as $row) {
$stmt->execute([$row['email'], $row['name']]);
if ($stmt->rowCount() > 0) {
$inserted++;
}
}
}
$pdo->commit();
return ['success' => true, 'inserted' => $inserted];
} catch (Exception $e) {
$pdo->rollBack();
return ['success' => false, 'error' => $e->getMessage()];
}
}
使用事务(复杂业务逻辑)
function insertWithTransaction($pdo, $email, $name, $phone) {
$pdo->beginTransaction();
try {
// 使用 SELECT ... FOR UPDATE 加行锁
$check = $pdo->prepare("SELECT id FROM users WHERE email = ? OR phone = ? FOR UPDATE");
$check->execute([$email, $phone]);
if ($check->rowCount() > 0) {
$pdo->rollBack();
return ['success' => false, 'message' => '邮箱或手机号已存在'];
}
$insert = $pdo->prepare("INSERT INTO users (email, name, phone) VALUES (?, ?, ?)");
$insert->execute([$email, $name, $phone]);
$pdo->commit();
return ['success' => true, 'id' => $pdo->lastInsertId()];
} catch (Exception $e) {
$pdo->rollBack();
return ['success' => false, 'error' => $e->getMessage()];
}
}
使用唯一ID生成策略
// 生成基于时间的UUID,避免重复
function generateUniqueId($prefix = '') {
$time = microtime(true);
$rand = random_int(10000, 99999);
$uniq = uniqid();
return $prefix . md5($time . $rand . $uniq . $_SERVER['REMOTE_ADDR']);
}
// 使用 Snowflake 算法(分布式ID)
class SnowflakeIdGenerator {
private $workerId;
private $sequence = 0;
private $lastTimestamp = -1;
public function generate() {
$timestamp = intval(microtime(true) * 1000);
if ($timestamp === $this->lastTimestamp) {
$this->sequence = ($this->sequence + 1) & 4095;
if ($this->sequence === 0) {
while ($timestamp <= $this->lastTimestamp) {
$timestamp = intval(microtime(true) * 1000);
}
}
} else {
$this->sequence = 0;
}
$this->lastTimestamp = $timestamp;
return (($timestamp - 1609459200000) << 22) | ($this->workerId << 12) | $this->sequence;
}
}
最佳实践建议
- 生产环境优先使用方案1(数据库唯一约束 +
ON DUPLICATE KEY UPDATE) - 结合前后端验证:
- 前端通过 AJAX 实时验证唯一性
- 后端二次验证
- 日志记录:记录重复插入的尝试
function logDuplicateAttempt($pdo, $data) { $log = $pdo->prepare("INSERT INTO duplicate_logs (data, ip, time) VALUES (?, ?, NOW())"); $log->execute([json_encode($data), $_SERVER['REMOTE_ADDR']]); }
根据你的具体业务场景选择最合适的方案,一般建议 数据库约束 + 业务逻辑验证 组合使用。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播