---
title: "DB-PHP 寫入 MySQL（HTML 表單資料） (tc)"
description: 本文說明如何用 PHP 接收 HTML 表單送出的資料，再寫入 MySQL 資料庫，並以 Prepared Statement 避免 SQL injection。
url: "https://aaronchan.com/zh/kb/php-write-mysql-form-data/"
updated: "2026-09-18"
category: "Code-php+MySQL (tc)"
---

# DB-PHP 寫入 MySQL（HTML 表單資料） (tc)

本文說明如何用 PHP 接收 HTML 表單（HTML form）送出的資料，再寫入（INSERT）MySQL 資料庫，並以 Prepared Statement（預處理語句）避免 SQL injection。

## 🧩 概念與資料流程

當用戶在網頁表單輸入資料並按送出，資料會經 `POST` 方法送到 PHP 腳本，再由 PHP 寫入 MySQL。整個流程：

| 步驟 | 動作 |
| --- | --- |
| 1 | HTML 表單用 `method="post"` 把資料送到 PHP 腳本（`action` 指向 `insert.php`） |
| 2 | PHP 用 `$_POST` 讀取各欄位的值 |
| 3 | 驗證（validate）+ 清理（`trim`、`filter_var`） |
| 4 | 用 Prepared Statement 綁定參數，執行 `INSERT` |
| 5 | 回傳成功／失敗訊息，關閉連線 |

⚠️ **最常見的錯誤**：直接把 `$_POST` 的值拼進 SQL 字串（string concatenation）。這是 SQL injection（SQL 注入）的溫床，任何來自用戶的輸入都必須用 Prepared Statement 處理。

---

## 📋 前置準備：建立資料庫與資料表

先建立資料表，記得用 `utf8mb4` 編碼支援中文：

 Copy

```
CREATE DATABASE IF NOT EXISTS demo
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

CREATE TABLE contacts (
  id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  name       VARCHAR(100) NOT NULL,
  email      VARCHAR(190) NOT NULL,
  message    TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

---

## 📝 HTML 表單範例

每個 input 的 `name` 屬性，就是 PHP 讀取時 `$_POST` 的鍵（key）：

 Copy

```
<!-- index.html: the form sends data to insert.php via POST -->
<form action="insert.php" method="post">
  <label>Name: <input type="text" name="name" required></label><br>
  <label>Email: <input type="email" name="email" required></label><br>
  <label>Message: <textarea name="message" required></textarea></label><br>
  <button type="submit">Submit</button>
</form>
```

---

## 💾 寫入 MySQL（mysqli 程序式 + Prepared Statement）

 Copy

```
<?php
// insert.php — write form data into MySQL (mysqli, prepared statement)
$conn = mysqli_connect('localhost', 'app_user', 'password', 'demo');
if (!$conn) {
    die('Connection failed: ' . mysqli_connect_error());
}
mysqli_set_charset($conn, 'utf8mb4');   // support Chinese, avoid mojibake

// 1. Read and validate form data
$name    = trim($_POST['name'] ?? '');
$email   = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');

if ($name === '' || $message === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
    exit('Invalid input');             // stop if any required field is missing
}

// 2. Prepare an INSERT statement with ? placeholders
$stmt = mysqli_prepare($conn, 'INSERT INTO contacts (name, email, message) VALUES (?, ?, ?)');

// 3. Bind variables: "sss" = three strings, in order
mysqli_stmt_bind_param($stmt, 'sss', $name, $email, $message);

// 4. Execute and check the result
if (mysqli_stmt_execute($stmt)) {
    $new_id = mysqli_insert_id($conn);   // id of the newly inserted row
    echo "Saved successfully. New record id: $new_id";
} else {
    echo 'Error: ' . mysqli_stmt_error($stmt);
}

mysqli_stmt_close($stmt);
mysqli_close($conn);
?>
```

`mysqli_stmt_bind_param()` 的第一個參數是型別字串（type string），每個字元代表一個欄位的資料型別：

| 字元 | 型別 | 說明 |
| --- | --- | --- |
| s | string | 字串（最常用） |
| i | integer | 整數 |
| d | double | 浮點數 |
| b | blob | 二進位（圖片、PDF 等） |

---

## 🛡️ PDO 版本（更推薦）

PDO 支援多種資料庫，且可用具名佔位符（named placeholder，`:name`），更易維護：

 Copy

```
<?php
// insert.php — write form data into MySQL (PDO, recommended)
$dsn = 'mysql:host=localhost;dbname=demo;charset=utf8mb4';
$pdo = new PDO($dsn, 'app_user', 'password', [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,  // throw exceptions on error
    PDO::ATTR_EMULATE_PREPARES   => false,                  // use native prepared statements
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

// Read and validate form data
$name    = trim($_POST['name'] ?? '');
$email   = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');

if ($name === '' || $message === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
    exit('Invalid input');
}

// Prepared statement with named placeholders
$sql  = 'INSERT INTO contacts (name, email, message) VALUES (:name, :email, :message)';
$stmt = $pdo->prepare($sql);
$stmt->execute([
    ':name'    => $name,
    ':email'   => $email,
    ':message' => $message,
]);

$new_id = (int) $pdo->lastInsertId();   // id of the newly inserted row
echo "Saved successfully. New record id: $new_id";
?>
```

---

## 🔀 mysqli vs PDO 快速對照

| 項目 | mysqli | PDO |
| --- | --- | --- |
| 佔位符（placeholder） | `?`（位置型） | `?` 或 `:name`（具名） |
| 綁定參數 | `mysqli_stmt_bind_param()` | `execute(array)` 或 `bindParam()` |
| 支援資料庫 | 只支援 MySQL | 多種資料庫 |
| 錯誤處理 | 手動檢查 | 可拋 Exception（try-catch） |
| 推薦程度 | 一般 | ✅ 更推薦（跨庫、易維護） |

---

## ⚠️ 注意事項

| 注意事項 | 說明 |
| --- | --- |
| 必用 Prepared Statement | 直接拼字串有 SQL injection 風險，綁定參數才安全 |
| 驗證輸入 | 檢查 required 欄位、email 格式（`filter_var`） |
| 設 charset `utf8mb4` | 否則中文會變亂碼 |
| 最小權限 | 資料庫用戶只給 `INSERT` 權限，不要用 root |
| 不輸出 raw SQL error | 錯誤只寫 log，回傳通用訊息給用戶 |
| CSRF 防護 | 表單加 token，防止跨站請求偽造 |
| 防重複提交 | 用 PRG pattern（POST-Redirect-GET），避免 refresh 重複 insert |

---

## 🔗 參考連結

| 來源 | 連結 |
| --- | --- |
| PHP Manual — mysqli Prepared Statements | [php.net](https://www.php.net/manual/en/mysqli.quickstart.prepared-statements.php) |
| PHP Manual — PDO Prepared Statements | [php.net](https://www.php.net/manual/en/pdo.prepared-statements.php) |
| W3Schools — PHP MySQL Prepared Statements | [w3schools.com](https://www.w3schools.com/php/php_mysql_prepared_statements.asp) |
| Tutorial Republic — Prepared Statements | [tutorialrepublic.com](https://www.tutorialrepublic.com/php-tutorial/php-mysql-prepared-statements.php) |
| IONOS — Insert HTML form into MySQL | [ionos.com](https://www.ionos.com/digitalguide/websites/web-development/use-php-to-insert-information-into-a-mysqlmariadb-database-from-an-html-form) |
