第 4 章:PHP 连接 MySQL:使用 mysqli 执行数据库操作
章节介绍
- 章节学习目标:通过本章学习,您将掌握使用 PHP 的 mysqli 扩展连接 MySQL 数据库的标准流程,学会在 PHP 脚本中安全地执行 SQL 语句并处理返回结果。这是将静态数据处理能力转化为动态 Web 应用的关键一步。
- 在整个教程中的作用:本章是承前启后的核心章节。它将前几章学习的 SQL 知识融入到 PHP 编程中,使您能够构建出真正与数据库交互的动态网站。至此,您将具备开发基础数据驱动型应用的能力。
- 与前面章节的衔接 :本章将直接使用第 2、3 章中创建和优化的
users数据表作为操作对象,将之前手写的 SQL 命令,转化为由 PHP 脚本动态生成和执行。 - 本章主要内容概览 :我们将首先介绍 mysqli 扩展的两种编程风格,然后学习建立和关闭数据库连接。接着,通过大量示例掌握执行查询(SELECT)和操作(INSERT, UPDATE, DELETE)语句的方法。本章重中之重是理解 SQL 注入攻击的原理,并学会使用预处理语句(Prepared Statements)这一唯一正确的方式来防范它。最后,我们将通过一个实战项目"用户注册与展示系统"来巩固所有知识。
核心概念讲解
1. mysqli 扩展简介
mysqli(MySQL Improved extension)是 PHP 用于操作 MySQL 数据库的官方推荐扩展。它支持 MySQL 4.1 及以上版本,提供了面向对象和面向过程两种编程接口,并支持预处理语句、事务等高级特性。与古老的mysql扩展相比,mysqli在安全性、性能和功能上都有显著提升。
2. 两种编程风格
- 面向对象风格 :通过创建
mysqli类的对象来调用方法。代码更清晰、易于维护,是现代 PHP 开发的首选方式。 - 面向过程风格 :通过一系列以
mysqli_为前缀的函数来操作。虽然直接,但在复杂场景下代码组织性较差。
本章将以面向对象风格为主要讲解对象,因为它更符合现代编程思想,并在关键的安全特性(如预处理语句)上语法更直观。
3. 建立数据库连接
连接到 MySQL 数据库需要四个核心参数:服务器地址、用户名、密码和数据库名。连接过程可能失败(如密码错误、服务器未启动),因此必须进行错误检查。
4. 执行 SQL 与处理结果
- 执行查询(SELECT) :使用
query()方法执行 SELECT 语句,返回一个mysqli_result结果集对象。我们需要从这个对象中逐行取出数据(常用fetch_assoc()方法)进行处理。 - 执行操作(INSERT, UPDATE, DELETE) :同样使用
query()方法,但返回的是布尔值,表示操作成功与否。可以通过affected_rows属性获取受影响的行数。
5. SQL 注入攻击与预处理语句(核心安全概念)
- SQL 注入原理:攻击者通过将恶意 SQL 代码"注入"到 Web 应用的输入参数中,从而欺骗后端数据库执行非预期的命令。这是 Web 安全领域的头号威胁(OWASP Top 10 长期位居榜首)。
- 危险案例 :假设登录查询为
"SELECT * FROM users WHERE username = '$username' AND password = '$password'"。如果用户在用户名输入框输入admin' --,那么最终的 SQL 会变成SELECT * FROM users WHERE username = 'admin' -- ' AND password = '...'。--是 SQL 注释符,它使得密码检查部分被注释掉,攻击者可能绕过密码验证,以管理员身份登录。 - 唯一正确的防护手段:预处理语句 。它的原理是将 SQL 语句的结构 (模板)与数据 分离发送给数据库。数据库先编译 SQL 结构,再将用户输入的数据作为纯参数绑定上去执行。这样,即使用户输入中包含 SQL 关键字,也只会被当作普通字符串数据,而不会被解析为 SQL 指令。mysqli 通过
prepare(),bind_param(),execute()三个步骤来实现。
代码示例
示例 1:建立数据库连接与错误处理
php
<?php
// 数据库连接配置
$servername = "localhost"; // MySQL服务器地址,通常为localhost
$username = "root"; // MySQL用户名
$password = "your_password"; // MySQL密码
$dbname = "my_php_db"; // 要连接的数据库名,请确保已创建
// 创建面向对象的mysqli连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检查连接是否成功
if ($conn->connect_error) {
// 连接失败时,在生产环境中应记录日志,对用户展示友好但非详细的错误信息
die("连接失败: " . $conn->connect_error . "。请联系管理员。");
} else {
echo "数据库连接成功!<br>";
// 设置客户端字符集为UTF-8,避免中文乱码
if (!$conn->set_charset("utf8mb4")) {
echo "错误:设置字符集失败 (" . $conn->error . ")<br>";
}
}
// 后续的数据库操作代码将放在这里...
// 在脚本最后,记得关闭连接(虽然不是必须,但显式关闭是良好习惯)
$conn->close();
echo "<br>数据库连接已关闭。";
?>
预期输出(连接成功时):
数据库连接成功!
数据库连接已关闭。
示例 2:执行 SELECT 查询并处理结果集
php
<?php
// ... 连接数据库的代码同上(假设 $conn 已成功创建)...
$sql = "SELECT id, username, email, created_at FROM users ORDER BY created_at DESC";
$result = $conn->query($sql); // 执行查询
// 检查查询是否成功执行并有数据返回
if ($result === false) {
die("查询失败: " . $conn->error); // 查询语句有语法错误时会执行这里
}
echo "<h3>用户列表</h3>";
echo "<table border='1'><tr><th>ID</th><th>用户名</th><th>邮箱</th><th>注册时间</th></tr>";
// 判断结果集中是否有行
if ($result->num_rows > 0) {
// 使用 fetch_assoc() 逐行获取关联数组(列名 => 值)
while($row = $result->fetch_assoc()) {
echo "<tr>";
echo "<td>" . htmlspecialchars($row["id"]) . "</td>"; // 使用htmlspecialchars防止XSS
echo "<td>" . htmlspecialchars($row["username"]) . "</td>";
echo "<td>" . htmlspecialchars($row["email"]) . "</td>";
echo "<td>" . $row["created_at"] . "</td>"; // 日期时间通常无需转义
echo "</tr>";
}
} else {
echo "<tr><td colspan='4'>暂无用户数据</td></tr>";
}
echo "</table>";
// 释放结果集内存
$result->close();
$conn->close();
?>
预期输出:一个包含用户数据的 HTML 表格。
示例 3:执行 INSERT 操作(危险的不安全方式 - 仅用于演示注入风险)
php
<?php
// ... 连接数据库的代码同上 ...
// *** 警告:以下代码存在严重SQL注入漏洞,绝对不要用于实际项目!***
$user_input_username = $_POST['username']; // 假设来自用户表单
$user_input_email = $_POST['email'];
// 直接拼接用户输入到SQL语句中,是万恶之源
$sql = "INSERT INTO users (username, email) VALUES ('$user_input_username', '$user_input_email')";
if ($conn->query($sql) === TRUE) {
echo "新记录插入成功, 最后插入的ID是: " . $conn->insert_id;
} else {
echo "错误: " . $sql . "<br>" . $conn->error;
}
$conn->close();
?>
攻击演示 :如果攻击者在username输入框输入:hacker', 'hacker@evil.com'); DROP TABLE users; -- 。
最终 SQL 语句变为:
sql
INSERT INTO users (username, email) VALUES ('hacker', 'hacker@evil.com'); DROP TABLE users; -- ', '...')
这将导致users表被删除,造成灾难性后果。
示例 4:使用预处理语句安全地执行 INSERT 操作(正确的防护方式)
php
<?php
// ... 连接数据库的代码同上 ...
// 1. 准备(Prepare)SQL语句模板,使用问号 `?` 作为参数占位符
$sql = "INSERT INTO users (username, email) VALUES (?, ?)";
$stmt = $conn->prepare($sql); // 返回一个预处理语句对象
if ($stmt === false) {
die("准备语句失败: " . $conn->error);
}
// 2. 绑定参数(Bind parameters)
// "ss" 表示两个参数都是字符串类型(s: string, i: integer, d: double, b: blob)
$stmt->bind_param("ss", $username, $email);
// 3. 设置参数并执行(Execute)
$username = "安全用户";
$email = "safe@example.com";
if ($stmt->execute()) {
echo "记录插入成功, ID: " . $stmt->insert_id;
} else {
echo "执行失败: " . $stmt->error;
}
// 可以重复使用同一个预处理语句,只需重新赋值变量并再次 execute()
$username = "另一个用户";
$email = "another@example.com";
$stmt->execute();
echo "<br>另一条记录插入成功, ID: " . $stmt->insert_id;
// 4. 关闭预处理语句和连接
$stmt->close();
$conn->close();
?>
示例 5:使用预处理语句执行 SELECT 查询
php
<?php
// ... 连接数据库的代码同上 ...
// 假设我们要查询某个邮箱的用户
$search_email = "safe@example.com";
// 准备带占位符的SELECT语句
$sql = "SELECT id, username FROM users WHERE email = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $search_email); // 绑定邮箱参数
$stmt->execute();
// 获取结果
$result = $stmt->get_result(); // 从预处理语句获取结果集对象
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
echo "ID: " . $row['id'] . " - 用户名: " . htmlspecialchars($row['username']) . "<br>";
}
} else {
echo "没有找到邮箱为 " . htmlspecialchars($search_email) . " 的用户。";
}
$stmt->close();
$conn->close();
?>
实战项目:用户注册与展示系统
项目需求分析和技术方案
- 目标:构建一个简单的 Web 系统,包含用户注册功能和用户信息展示页面。
- 功能:
- 注册页面(register.php):提供表单让用户输入用户名和邮箱。
- 处理注册逻辑(do_register.php):接收表单数据,使用预处理语句 安全地将数据插入
users表,并给出反馈。 - 用户列表页面(list_users.php):从
users表中读取所有用户,并以表格形式展示。
- 技术栈:HTML, PHP (mysqli), MySQL。
- 数据库 :使用之前章节创建的
users表,确保其有id(主键,自增),username(唯一),email,created_at(默认 CURRENT_TIMESTAMP)等字段。
分步骤实现代码和详细说明
步骤 1:创建项目文件结构
/project_chapter4
├── config/
│ └── database.php # 数据库连接配置文件
├── register.php # 用户注册表单页面
├── do_register.php # 处理注册的PHP脚本
├── list_users.php # 用户列表展示页面
└── index.php # 首页(可跳转到注册或列表)
步骤 2:编写数据库配置文件 (config/database.php)
php
<?php
// 数据库配置常量,便于全局使用和维护
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', 'your_password'); // 请替换为你的实际密码
define('DB_NAME', 'my_php_db');
/**
* 获取数据库连接
* @return mysqli 数据库连接对象
* @throws Exception 连接失败时抛出异常
*/
function getDbConnection() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
// 使用异常便于统一处理错误
throw new Exception('数据库连接失败: ' . $conn->connect_error);
}
$conn->set_charset("utf8mb4");
return $conn;
}
?>
步骤 3:编写注册表单页面 (register.php)
php
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户注册</title>
<style>
.error { color: red; }
.success { color: green; }
</style>
</head>
<body>
<h2>用户注册</h2>
<?php
// 显示可能的错误或成功信息(来自do_register.php的跳转)
if (isset($_GET['error'])) {
echo '<p class="error">' . htmlspecialchars($_GET['error']) . '</p>';
}
if (isset($_GET['success'])) {
echo '<p class="success">注册成功!您的用户名是: ' . htmlspecialchars($_GET['username']) . '</p>';
}
?>
<form action="do_register.php" method="POST">
<div>
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required minlength="3" maxlength="50">
<small>(3-50个字符)</small>
</div>
<div>
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</div>
<div>
<button type="submit">注册</button>
</div>
</form>
<p><a href="list_users.php">查看所有用户</a></p>
</body>
</html>
步骤 4:编写注册处理脚本 (do_register.php)
php
<?php
// 引入数据库配置
require_once 'config/database.php';
// 初始化变量
$username = $email = "";
$errors = [];
// 检查是否通过POST方法提交
if ($_SERVER["REQUEST_METHOD"] == "POST") {
// 基础输入验证和清理
$username = trim($_POST['username'] ?? "");
$email = trim($_POST['email'] ?? "");
if (empty($username)) {
$errors[] = "用户名不能为空。";
} elseif (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]{3,50}$/u', $username)) {
// 更严格的用户名规则:字母、数字、下划线、中文,3-50位
$errors[] = "用户名格式无效(仅允许字母、数字、下划线、中文,3-50位)。";
}
if (empty($email)) {
$errors[] = "邮箱不能为空。";
} elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "邮箱格式无效。";
}
// 如果验证通过,尝试连接数据库并插入
if (empty($errors)) {
try {
$conn = getDbConnection();
// **使用预处理语句防止SQL注入**
$sql = "INSERT INTO users (username, email) VALUES (?, ?)";
$stmt = $conn->prepare($sql);
if (!$stmt) {
throw new Exception("准备语句失败: " . $conn->error);
}
$stmt->bind_param("ss", $username, $email);
if ($stmt->execute()) {
// 插入成功,重定向回注册页并显示成功信息
header("Location: register.php?success=1&username=" . urlencode($username));
exit(); // 重定向后务必退出脚本
} else {
// 执行失败,通常是唯一约束冲突(用户名或邮箱已存在)
if ($conn->errno == 1062) { // MySQL 唯一键冲突错误码
$errors[] = "用户名或邮箱已被注册。";
} else {
$errors[] = "系统错误,请稍后再试。";
// 生产环境应记录详细错误日志:$stmt->error
}
}
$stmt->close();
$conn->close();
} catch (Exception $e) {
$errors[] = "数据库连接异常:" . $e->getMessage();
}
}
}
// 如果执行到这里,说明有错误发生,或者不是POST请求
// 将错误信息拼接,并重定向回注册页显示
$error_query = empty($errors) ? "" : "?error=" . urlencode(implode(' ', $errors));
// 也可以将用户名和邮箱通过session或GET传回以保持表单状态,这里简化处理
header("Location: register.php" . $error_query);
exit();
?>
步骤 5:编写用户列表页面 (list_users.php)
php
<?php
require_once 'config/database.php';
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<style>
table { border-collapse: collapse; width: 80%; margin: 20px auto; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #f2f2f2; }
tr:nth-child(even) { background-color: #f9f9f9; }
</style>
</head>
<body>
<h2 style="text-align: center;">注册用户列表</h2>
<?php
try {
$conn = getDbConnection();
$sql = "SELECT id, username, email, created_at FROM users ORDER BY created_at DESC";
$result = $conn->query($sql);
if ($result === false) {
throw new Exception("查询失败: " . $conn->error);
}
echo "<table>";
echo "<tr><th>ID</th><th>用户名</th><th>邮箱</th><th>注册时间</th></tr>";
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
echo "<tr>";
echo "<td>" . (int)$row['id'] . "</td>";
// 使用 htmlspecialchars 输出用户数据,防止XSS攻击
echo "<td>" . htmlspecialchars($row['username']) . "</td>";
echo "<td>" . htmlspecialchars($row['email']) . "</td>";
echo "<td>" . $row['created_at'] . "</td>";
echo "</tr>";
}
} else {
echo "<tr><td colspan='4' style='text-align: center;'>暂无用户数据</td></tr>";
}
echo "</table>";
$result->close();
$conn->close();
} catch (Exception $e) {
echo "<p class='error'>出错了: " . htmlspecialchars($e->getMessage()) . "</p>";
}
?>
<p style="text-align: center;"><a href="register.php">返回注册页面</a></p>
</body>
</html>
项目测试和部署指南
- 环境准备 :确保 PHP 和 MySQL 服务已启动,并已创建
my_php_db数据库和users表。 - 配置修改 :编辑
config/database.php,填入正确的数据库密码。 - 功能测试:
- 访问
register.php,填写表单并提交,应提示成功。 - 尝试注册重复的用户名或邮箱,应看到相应的错误提示。
- 访问
list_users.php,应看到所有已注册用户(包括刚注册的)按时间倒序排列。
- 安全测试:
- SQL 注入测试 :在注册表单的用户名中输入
test' OR '1'='1。在旧的不安全代码中,这可能引发错误或异常行为。但在我们的项目中,由于使用了预处理语句,这个输入会被安全地存储为普通的用户名字符串。你可以去列表页查看,用户名就是字面的test' OR '1'='1。这正是我们想要的效果------数据就是数据,不会被当作代码执行。 - XSS 测试 :在用户名中输入
<script>alert('xss')</script>。提交后,在列表页(list_users.php)查看。由于我们使用了htmlspecialchars()函数输出,这个脚本不会被浏览器执行,而是会显示为普通的文本<script>alert('xss')</script>。这成功防御了存储型 XSS 攻击。
项目扩展和优化建议
- 增加密码字段 :在
users表中添加password字段(使用 VARCHAR(255)),注册时要求输入密码,并使用password_hash()函数进行强散列存储。登录功能即可在此基础上实现。 - 分页功能 :当用户数量增多时,在
list_users.php中使用 SQL 的LIMIT子句实现分页查询。 - 前端增强:使用 JavaScript 进行表单的实时验证(如用户名可用性检查),提升用户体验。
- 代码组织 :将数据库操作(如
insertUser,getAllUsers)封装成独立的函数或类,使业务逻辑更清晰。 - Session 管理 :实现登录后,使用
$_SESSION来管理用户状态。
最佳实践
1. 安全第一:使用预处理语句
- 黄金法则 :任何包含用户输入(包括表单、Cookie, URL 参数)的 SQL 语句,都必须使用预处理语句(Prepared Statements)。这是防范 SQL 注入唯一有效且必要的方法。
- mysqli 或 PDO :PHP 有两个支持预处理的主流扩展:
mysqli和PDO。两者皆可,PDO 支持更多数据库驱动,语法稍统一。本章学习的mysqli预处理知识是核心基础。
2. 完善的错误处理
- 开发环境:可以显示详细错误以帮助调试(但不要暴露给普通用户)。
- 生产环境 :务必关闭
display_errors,将错误记录到日志文件(如使用error_log()函数)。对最终用户展示友好的通用错误信息,如"系统繁忙,请稍后再试"。 - 使用 Try-Catch:对于可能抛出异常的操作(如使用 PDO 时),使用 try-catch 块进行捕获和处理。
3. 连接管理
- 及时关闭 :虽然脚本结束后连接会自动关闭,但显式调用
close()方法(面向对象)或mysqli_close()(面向过程)是一个好习惯,尤其是在长脚本或循环中。 - 单一连接:一个脚本生命周期内,尽量使用一个数据库连接。不要在每个函数里都创建新连接。
4. 字符集设置
- 连接建立后,立即设置客户端字符集(通常为
utf8mb4),以确保 PHP 和 MySQL 之间的数据(尤其是中文)传输不会乱码。
php
$conn->set_charset("utf8mb4");
// 或面向过程:mysqli_set_charset($conn, "utf8mb4");
5. 输入验证与输出转义
- 深度防御 :预处理语句防 SQL 注入,但前端和后端仍需进行输入验证(如格式、长度、类型)。这能保证数据质量,并作为另一道安全屏障。
- 输出转义 :当将数据库中的数据输出到 HTML 页面时,必须使用
htmlspecialchars()函数进行转义,以防止跨站脚本攻击(XSS)。如实战项目中所演示。
6. 常见错误与避坑指南
- 忘记
bind_param的类型参数 :bind_param("ssi", ...)中的类型字符串必须与后面变量的数量和类型一一对应。 - 混淆
query()和prepare():query()用于执行不需要参数的简单 SQL;需要传入变量时,必须用prepare()。 - 在循环内重复
prepare:对于结构相同、仅数据不同的多次插入,应在一个循环外prepare一次,循环内只进行bind_param和execute,这能大幅提升性能。 - 未释放结果集 :对于返回较大结果集的查询,在使用完后调用
$result->free()或$result->close()可以及时释放内存。
练习题与挑战
基础练习题
- 【难度:★☆☆☆☆】连接与简单查询
- 题目 :编写一个 PHP 脚本
count_users.php,连接到你的my_php_db数据库,查询users表中的总用户数,并以"当前共有 [数字] 位注册用户。"的格式输出。
- 题目 :编写一个 PHP 脚本
- 提示 :使用
SELECT COUNT(*) AS total FROM users查询,$result->fetch_assoc()获取结果。 - 参考答案:
php
<?php
require_once 'config/database.php'; // 复用项目中的配置
try {
$conn = getDbConnection();
$result = $conn->query("SELECT COUNT(*) AS total FROM users");
$row = $result->fetch_assoc();
echo "当前共有 " . (int)$row['total'] . " 位注册用户。";
$result->close();
$conn->close();
} catch (Exception $e) {
echo "查询失败:" . $e->getMessage();
}
?>
- 【难度:★★☆☆☆】使用预处理语句更新数据
- 题目 :创建一个 PHP 脚本
update_email.php,接收 GET 参数id和new_email。使用预处理语句,将users表中对应id的用户的邮箱更新为new_email。请包含必要的参数验证(如id是否为数字,邮箱格式)和错误处理(如用户不存在)。
- 题目 :创建一个 PHP 脚本
- 提示 :SQL 语句为
UPDATE users SET email = ? WHERE id = ?。使用bind_param("si", $email, $id)。更新后使用$stmt->affected_rows判断是否更新成功。 - 参考答案思路:
php
// 验证id是正整数,验证邮箱格式
// 连接数据库
// 准备UPDATE预处理语句
// 绑定并执行
// 根据affected_rows输出成功或"用户不存在"信息
进阶练习题
- 【难度:★★★☆☆】实现分页查询
- 题目 :修改
list_users.php,实现分页功能。通过 GET 参数page(默认为 1)和per_page(默认为 5)来控制每页显示的记录数和当前页码。在页面底部显示"上一页"、"下一页"的链接(如果存在)。
- 题目 :修改
- 提示 :SQL 使用
LIMIT ?, ?。第一个问号是偏移量(page - 1) * per_page,第二个问号是per_page。需要先查询一次总记录数来计算总页数。 - 关键 SQL :
SELECT ... FROM users ORDER BY id DESC LIMIT ?, ?
- 【难度:★★★☆☆】封装数据库操作函数
- 题目 :创建一个文件
db_functions.php,将常用的数据库操作封装成函数。
- 题目 :创建一个文件
db_insert_user($username, $email): 插入用户,返回新 ID 或 false。db_get_user_by_id($id): 根据 ID 获取单个用户信息(关联数组)。db_get_all_users($limit=null): 获取所有用户,可选限制条数。- 要求:所有函数内部必须使用预处理语句,并妥善处理错误。
- 提示 :函数内使用
global $conn;或通过参数传递连接对象。考虑函数失败时返回什么(如 false),错误信息如何传递(如抛出异常或设置全局错误变量)。
综合挑战题
- 【难度:★★★★☆】简易留言板系统
- 题目:独立设计并实现一个包含前后台的简易留言板系统。
- 需求:
- 数据库设计:创建
messages表,包含id,author(留言者名),content(留言内容),created_at。 - 前台页面(
index.php):展示所有留言(最新在前),并提供一个表单让访客提交新留言。 - 后台管理页面(
admin.php):以表格展示留言,每条留言后有一个"删除"按钮/链接。 - 删除功能(
delete.php):接收留言 ID,执行删除操作(务必使用预处理语句)。 - 安全要求:
- 防 SQL 注入(预处理语句)。
- 防 XSS(输出留言内容时使用
htmlspecialchars)。 - (可选)为删除操作添加简单的管理员密码验证(如通过 Session)。
- 扩展挑战:为留言添加回复功能,或实现留言分页。
章节总结
- 本章重点知识回顾:
- mysqli 扩展:PHP 操作 MySQL 的现代方式,支持面向对象和过程两种风格。
- 连接与关闭:使用服务器、用户名、密码、数据库名四要素建立连接,并务必设置字符集和检查错误。
- 执行 SQL:
query()+fetch_assoc()用于处理 SELECT 查询结果。query()配合affected_rows和insert_id用于处理 INSERT, UPDATE, DELETE 操作。
- 安全核心------预处理语句 :通过
prepare(),bind_param(),execute()三步,将 SQL 结构与数据分离,是防御 SQL 注入攻击的唯一正确方法,必须牢记并应用于所有涉及用户输入的数据库操作中。 - 实战流程:通过"用户注册与展示系统"项目,体验了从表单到数据库,再从数据库到页面的完整数据流,并实践了安全编码。
- 技能掌握要求:学完本章,你应该能够不借助参考,独立完成以下任务:
- 在 PHP 脚本中安全地连接到 MySQL 数据库。
- 使用预处理语句执行带参数的 INSERT, UPDATE 操作。
- 执行 SELECT 查询,并将结果以数组形式取出,在网页中展示。
- 清晰解释什么是 SQL 注入,以及为什么预处理语句可以防止它。
- 进一步学习建议:
- 探索PDO(PHP Data Objects) 扩展,它是另一个强大且支持多种数据库的数据访问抽象层,其预处理语句的语法略有不同。
- 了解MySQL 事务 的概念,学习如何在 mysqli 中使用
begin_transaction(),commit(),rollback()来保证一组操作的原子性。 - 学习更复杂的SQL JOIN 查询,并在 PHP 中处理多表关联的结果集,这将在下一个综合实战章节中用到。
- 深入 Web 安全领域,了解除了 SQL 注入外的其他常见漏洞,如跨站请求伪造(CSRF)、会话固定等,并学习相应的防护措施。安全是一个持续的学习过程。