mysql_* 系列函數自 PHP 5.5 棄用、PHP 7.0 移除。現代專案請使用 mysqli (MySQL Improved) 或 PDO。本文提供 mysqli 面向物件風格的完整 CRUD 範例,涵蓋預處理語句、事務、錯誤處理等最佳實踐。
CREATE DATABASE `demo` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `demo`;
CREATE TABLE `users` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL UNIQUE,
`email` VARCHAR(100) NOT NULL,
`password_hash` VARCHAR(255) NOT NULL,
`role` ENUM('user','admin') DEFAULT 'user',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
<?php
declare(strict_types=1);
class Database {
private mysqli $mysqli;
public function __construct(
string $host = '127.0.0.1',
string $user = 'root',
string $pass = '',
string $db = 'demo',
int $port = 3306,
string $charset = 'utf8mb4'
) {
$this->mysqli = new mysqli($host, $user, $pass, $db, $port);
if ($this->mysqli->connect_errno) {
throw new RuntimeException("MySQL 連線失敗: {$this->mysqli->connect_error}");
}
$this->mysqli->set_charset($charset);
// 開啟異常模式:錯誤自動拋出 mysqli_sql_exception
$this->mysqli->report_mode = MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT;
}
public function getConnection(): mysqli {
return $this->mysqli;
}
// 禁止複製
private function __clone() {}
public function __wakeup() {}
}
<?php
$db = new Database();
$mysqli = $db->getConnection();
$username = 'john_doe';
$email = 'john@example.com';
$password = password_hash('secret123', PASSWORD_DEFAULT); // 密碼雜湊
$role = 'user';
$stmt = $mysqli->prepare("INSERT INTO `users` (`username`, `email`, `password_hash`, `role`) VALUES (?, ?, ?, ?)");
$stmt->bind_param('ssss', $username, $email, $password, $role);
$stmt->execute();
$newId = $mysqli->insert_id;
echo "新增成功,ID: $newId";
$stmt->close();
<?php
// 單筆查詢
$stmt = $mysqli->prepare("SELECT `id`, `username`, `email`, `role`, `created_at` FROM `users` WHERE `id` = ?");
$stmt->bind_param('i', $id);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc(); // 關聯陣列
$stmt->close();
if ($user) {
echo "用戶: {$user['username']} ({$user['email']})";
} else {
echo "查無此用戶";
}
// 多筆查詢 + 分頁
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 15;
$offset = ($page - 1) * $perPage;
$stmt = $mysqli->prepare("SELECT `id`, `username`, `email`, `role` FROM `users` ORDER BY `id` DESC LIMIT ?, ?");
$stmt->bind_param('ii', $offset, $perPage);
$stmt->execute();
$users = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt->close();
foreach ($users as $u) {
echo "{$u['username']} - {$u['email']}
";
}
<?php
$stmt = $mysqli->prepare("UPDATE `users` SET `email` = ?, `role` = ? WHERE `id` = ?");
$stmt->bind_param('ssi', $newEmail, $newRole, $userId);
$newEmail = 'john.new@example.com';
$newRole = 'admin';
$userId = 1;
$stmt->execute();
$affected = $stmt->affected_rows;
echo "更新了 $affected 筆資料";
$stmt->close();
<?php
$stmt = $mysqli->prepare("DELETE FROM `users` WHERE `id` = ?");
$stmt->bind_param('i', $deleteId);
$deleteId = 999; // 不存在的 ID
$stmt->execute();
echo "刪除 {$stmt->affected_rows} 筆";
$stmt->close();
<?php
$mysqli->begin_transaction();
try {
// 操作 1:扣款
$stmt = $mysqli->prepare("UPDATE `accounts` SET `balance` = `balance` - ? WHERE `user_id` = ? AND `balance` >= ?");
$stmt->bind_param('dii', $amount, $fromId, $amount);
$stmt->execute();
if ($stmt->affected_rows === 0) throw new Exception("餘額不足");
$stmt->close();
// 操作 2:入帳
$stmt = $mysqli->prepare("UPDATE `accounts` SET `balance` = `balance` + ? WHERE `user_id` = ?");
$stmt->bind_param('di', $amount, $toId);
$stmt->execute();
$stmt->close();
// 記錄流水
$stmt = $mysqli->prepare("INSERT INTO `transactions` (`from_id`, `to_id`, `amount`) VALUES (?, ?, ?)");
$stmt->bind_param('iid', $fromId, $toId, $amount);
$stmt->execute();
$stmt->close();
$mysqli->commit();
echo "轉帳成功";
} catch (Throwable $e) {
$mysqli->rollback();
echo "轉帳失敗: " . $e->getMessage();
}
<?php
// 1. 開啟異常模式(推薦)
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
try {
$mysqli = new mysqli('localhost', 'user', 'pass', 'db');
// ... 所有操作若失敗自動拋出 mysqli_sql_exception
} catch (mysqli_sql_exception $e) {
// 記錄日誌
error_log("DB Error [{$e->getCode()}]: {$e->getMessage()}");
// 回應用戶友善訊息
http_response_code(500);
die('系統暫時無法處理請求,請稍後再試');
}
// 2. 手動檢查錯誤(不開啟異常模式時)
if ($mysqli->errno) {
error_log("MySQL Error {$mysqli->errno}: {$mysqli->error}");
}
| 項目 | 做法 |
|---|---|
| SQL 注入 | 全程使用 prepare() + bind_param(),絕不拼接變數 |
| 密碼儲存 | password_hash() + password_verify(),禁用 MD5/SHA1 |
| 權限最小化 | 資料庫帳號僅授予必要權限(SELECT/INSERT/UPDATE/DELETE) |
| 字符集 | 統一 utf8mb4,支援 Emoji 與完整 Unicode |
| 錯誤資訊 | 生產環境關閉詳細錯誤輸出,記錄至日誌檔 |
| 連線復用 | 長連線 mysqli::persistent 或連線池(PHP-FPM 自動復用) |
<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=demo;charset=utf8mb4', 'user', 'pass', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // 使用真實預處理
]);
// Insert
$stmt = $pdo->prepare("INSERT INTO users (username, email, password_hash) VALUES (?, ?, ?)");
$stmt->execute([$username, $email, $hash]);
$id = $pdo->lastInsertId();
// Select
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
// Update
$stmt = $pdo->prepare("UPDATE users SET email = ? WHERE id = ?");
$stmt->execute([$newEmail, $id]);
// Delete
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
// Transaction
$pdo->beginTransaction();
try {
// ... operations
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
throw $e;
}
mysql_*,改用 mysqli (OO 風格) 或 PDOmysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT) 讓錯誤自動拋出異常password_hash() 儲存,password_verify() 驗證try-catch + rollback()