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

PHP MySQL SELECT 查詢實戰:從基礎語法到預處理防注入最佳實踐

     編輯:PHP 相關知識

SELECT 是數據庫操作的核心。本文系統梳理 PHP 執行 SELECT 查詢的完整流程:連接、編碼、查詢構建、結果遍歷、資源釋放,重點對比 mysqli 與 PDO 兩大擴展的用法差異,並確立 預處理語句 作為防注入標準。

基礎流程圖解

1. 建立連接 (mysqli_connect / new PDO)
2. 設置字符集 (SET NAMES utf8mb4)
3. 構建 SQL (參數化佔位符 ? / :name)
4. 預處理 (prepare)
5. 綁定參數 (bind_param / execute([...]))
6. 執行 (execute)
7. 獲取結果集 (get_result / fetchAll)
8. 遍歷數據 (fetch_assoc / foreach)
9. 釋放結果 (free_result / null)
10. 關閉連接 (close / null)

方案一:mysqli 面向過程風格(兼容舊代碼)

<?php
$host = 'localhost'; $user = 'user'; $pass = 'pass'; $db = 'testdb';

$link = mysqli_connect($host, $user, $pass, $db);
if (!$link) { die('連接失敗: ' . mysqli_connect_error()); }

// 關鍵:設置字符集防止中文亂碼
mysqli_set_charset($link, 'utf8mb4');

// 預處理防注入
$sql = "SELECT uri, title FROM articles WHERE status = ? AND category_id = ? ORDER BY created_at DESC LIMIT ?";
$stmt = mysqli_prepare($link, $sql);
mysqli_stmt_bind_param($stmt, 'iii', $status, $catId, $limit);

$status = 1; $catId = 5; $limit = 10;
mysqli_stmt_execute($stmt);

$result = mysqli_stmt_get_result($stmt); // 需 mysqlnd 驅動
while ($row = mysqli_fetch_assoc($result)) {
    echo "<p><strong>{$row['title']}</strong>: {$row['uri']}</p>";
}

mysqli_free_result($result);
mysqli_stmt_close($stmt);
mysqli_close($link);
?>

方案二:PDO 面向對象風格(現代首選)

<?php
$dsn = "mysql:host=localhost;dbname=testdb;charset=utf8mb4";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
];

try {
    $pdo = new PDO($dsn, 'user', 'pass', $options);
} catch (PDOException $e) {
    die('連接失敗: ' . $e->getMessage());
}

$sql = "SELECT uri, title FROM articles WHERE status = :status AND category_id = :cat ORDER BY created_at DESC LIMIT :limit";
$stmt = $pdo->prepare($sql);
$stmt->execute([
    'status' => 1,
    'cat' => 5,
    'limit' => 10,
]);

foreach ($stmt as $row) {
    echo "<p><strong>{$row['title']}</strong>: {$row['uri']}</p>";
}

// $stmt = null; // 釋放語句句柄
// $pdo = null;  // 關閉連接
?>

關鍵知識點深度解析

1. 字符集:必須用 utf8mb4

  • utf8 (MySQL 別名 utf8mb3) 僅支持 BMP 平面,**無法存儲 Emoji、生僻字**。
  • 連接、表、列、數據庫四層均應為 utf8mb4 + utf8mb4_unicode_ci。
  • PDO DSN 中 `charset=utf8mb4` + `SET NAMES` 雙重保障。

2. 預處理原理與防注入

-- 服務端解析階段即確定查詢計劃,參數作為數據傳輸,永不參與 SQL 解析
PREPARE stmt FROM 'SELECT * FROM users WHERE name = ?';
SET @n = 'admin'' OR 1=1 --';
EXECUTE stmt USING @n; -- 安全:參數被視為字面字符串,不會改變邏輯
  • 禁止任何形式的字符串拼接 SQL:"WHERE name = '$name'"。
  • 識別符(表名、列名、排序字段)無法參數化,需通過白名單驗證:in_array($orderBy, $allowedColumns)。

3. 結果集獲取模式對比

模式mysqliPDO適用場景
緩衝模式 (默認)mysqli_stmt_get_result / fetch_allfetchAll()結果集較小(<10萬行),需隨機訪問、多次遍歷
非緩衝/流式mysqli_stmt_fetch() 逐行fetch() 逐行迭代大數據集、導出 CSV、內存受限

4. 隨機查詢優化

-- 低效:全表排序
SELECT * FROM articles ORDER BY RAND() LIMIT 1;

-- 高效:主鍵範圍隨機 (需連續自增 ID)
SELECT * FROM articles 
WHERE id >= (SELECT FLOOR(RAND() * (SELECT MAX(id) FROM articles))) 
ORDER BY id LIMIT 1;

常見錯誤與修正

錯誤代碼問題修正
mysql_query("SET NAMES GBK")使用已移除的 mysql_* 擴展改用 mysqli/PDO + utf8mb4
$sql = "SELECT * FROM t WHERE x = $input"直接拼接變量,SQL 注入高危預處理 + 參數綁定
未檢查連接/查詢返回值靜默失敗,難以排查PDO 設置 ERRMODE_EXCEPTION;mysqli 檢查 false
循環中執行查詢N+1 問題,性能災難JOIN、IN 子查詢、批量預處理

封裝建議:通用查詢構建器

<?php
class QueryBuilder
{
    public function __construct(private PDO $pdo) {}

    public function select(string $table, array $columns = ['*'], array $where = [], array $orderBy = [], int $limit = 0): array
    {
        $sql = "SELECT " . implode(', ', array_map(fn($c)=>"`$c`", $columns)) . " FROM `$table`";
        $params = [];

        if ($where) {
            $conditions = [];
            foreach ($where as $col => $val) {
                if (is_array($val)) { // IN 查詢
                    $placeholders = implode(',', array_fill(0, count($val), '?'));
                    $conditions[] = "`$col` IN ($placeholders)";
                    $params = array_merge($params, $val);
                } else {
                    $conditions[] = "`$col` = ?";
                    $params[] = $val;
                }
            }
            $sql .= " WHERE " . implode(' AND ', $conditions);
        }

        if ($orderBy) {
            $sql .= " ORDER BY " . implode(', ', $orderBy);
        }
        if ($limit > 0) {
            $sql .= " LIMIT $limit";
        }

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
}

// 使用
$qb = new QueryBuilder($pdo);
$articles = $qb->select('articles', ['title', 'uri'], ['status'=>1, 'category_id'=>[5,6]], ['created_at DESC'], 10);
?>
SELECT 看似簡單,但「字符集、預處理、流式讀取、索引優化」四大支柱缺一不可。養成「永不拼接 SQL」的肌肉記憶,是 PHP 開發者的職業底線。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved