首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 防止 MySQL 重複寫入:從查詢檢查到唯一索引的最佳實踐演進

     編輯:PHP 相關知識

問題場景

使用者註冊、搜尋關鍵字記錄、表單提交等場景,常面臨「避免重複資料寫入」的需求。早期常見做法是「先查再插」,但存在競態條件與效能問題。

方案演進對比

❌ 方案一:SELECT 檢查再 INSERT(不推薦)

<?php
// 經典反面教材
$username = $_GET['name'] ?? '';
$q = "SELECT * FROM usertable WHERE user_name = '$username'";
$rs = mysql_query($q);
if (mysql_num_rows($rs) === 0) {
    mysql_query("INSERT INTO student (user_name) VALUES ('$username')");
    echo "註冊成功";
} else {
    echo "用戶名已存在";
}

缺點:

  • SQL 注入風險(直接拼接變數)
  • 競態條件:高併發下兩請求同時通過 SELECT,皆執行 INSERT
  • 兩次資料庫往返,延遲較高
  • mysql_* 函式已移除(PHP 7.0+)

⚠️ 方案二:INSERT IGNORE / ON DUPLICATE KEY UPDATE

<?php
// 需先建立 UNIQUE INDEX
$pdo->exec("CREATE UNIQUE INDEX uk_username ON usertable(user_name)");

// 寫入
$stmt = $pdo->prepare("INSERT IGNORE INTO usertable (user_name) VALUES (?)");
$stmt->execute([$username]);

if ($stmt->rowCount() > 0) {
    echo "新增成功";
} else {
    echo "已存在";
}

優點: 原子性、單次往返、無競態。
缺點: INSERT IGNORE 會吞沒其他錯誤(如外鍵違反),除錯困難。

✅ 方案三:PDO + 唯一索引 + 例外處理(推薦)

<?php
try {
    $pdo = new PDO("mysql:host=localhost;dbname=test;charset=utf8mb4", 'user', 'pass', [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    ]);
    
    // 確保唯一索引存在(遷移腳本執行一次即可)
    $pdo->exec("CREATE UNIQUE INDEX IF NOT EXISTS uk_username ON usertable(user_name)");
    
    $stmt = $pdo->prepare("INSERT INTO usertable (user_name) VALUES (?)");
    $stmt->execute([$username]);
    echo "註冊成功";
    
} catch (PDOException $e) {
    if ($e->getCode() === '23000') { // SQLSTATE 23000: integrity constraint violation
        echo "用戶名已存在";
    } else {
        error_log("DB Error: " . $e->getMessage());
        echo "系統錯誤,請稍後再試";
    }
}

核心最佳實踐總表

層面建議
資料庫層務必加 UNIQUE INDEX,讓 DB 保證資料完整性
連線層使用 PDO,啟用 ERRMODE_EXCEPTION
查詢層全部使用 預備語句,防 SQL 注入
錯誤層捕獲 PDOException,依 SQLSTATE 碼區分業務邏輯
編碼層連線指定 charset=utf8mb4,支援完整 Unicode

進階:取得現有記錄 ID(Upsert 模式)

$stmt = $pdo->prepare("
    INSERT INTO usertable (user_name) VALUES (?)
    ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)
");
$stmt->execute([$username]);
$userId = $pdo->lastInsertId(); // 無論新增或已存在,都能拿到 ID
echo "用戶 ID: $userId";

總結

「先查後插」是反模式。正確做法:資料庫加唯一索引 + 預備語句 + 捕獲唯一鍵衝突例外。這樣既解決併發安全,又簡化程式碼,符合現代 PHP 開發標準。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved