PHP 连接与查询 MySQL 数据库:从入门到安全实践90
---
在现代Web开发中,PHP作为一种强大的服务器端脚本语言,其核心能力之一就是与数据库进行交互。无论是构建动态网站、API服务还是复杂的企业级应用,数据库都是存储和管理数据的基石。本文将全面深入地探讨PHP如何连接数据库(主要以MySQL为例,因为它是PHP最常用的搭档),并安全高效地执行数据查询操作。我们将从基础概念讲起,逐步覆盖数据库扩展的选择、连接方式、SQL查询的执行,以及最重要的——如何通过预处理语句(Prepared Statements)来防止SQL注入攻击,确保您的应用程序安全可靠。
1. 理解 PHP 与数据库交互的重要性
动态Web应用程序的魅力在于其能够根据用户的输入或后端数据生成不同的内容。这种动态性离不开数据库的支持。PHP负责接收用户请求,处理业务逻辑,然后与数据库进行沟通,存取数据,最终将处理结果返回给用户。因此,掌握PHP与数据库的连接与查询技术,是每一位PHP开发者必备的核心技能。
2. 选择合适的 PHP 数据库扩展
PHP提供了多种与数据库交互的方式,但主要推荐以下两种现代且安全的扩展:
MySQLi (MySQL Improved Extension):专为MySQL数据库设计,提供了面向对象和面向过程两种接口。它比老旧的mysql_*函数(已在PHP 7中移除)更安全、功能更强大。
PDO (PHP Data Objects):这是一个通用的数据库抽象层,支持多种数据库(如MySQL, PostgreSQL, SQLite, SQL Server等)。它提供了一致的API接口,意味着您可以在不改变核心代码逻辑的情况下切换不同的数据库系统。由于其灵活性和强大的功能,PDO通常是新项目的首选。
在本文中,我们将重点介绍MySQLi (面向对象) 和 PDO 两种方式,并建议读者在新项目中优先考虑使用PDO。
3. 数据库连接前的准备工作
在编写PHP代码之前,请确保您的开发环境已满足以下条件:
已安装PHP(建议PHP 7.4 或更高版本)。
已安装Web服务器(如Apache或Nginx)。
已安装MySQL或MariaDB数据库服务器。
已在MySQL中创建了数据库和表。例如,我们可以创建一个名为 `mydatabase` 的数据库和名为 `users` 的表:
CREATE DATABASE mydatabase;
USE mydatabase;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
password VARCHAR(255) NOT NULL,
registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (username, email, password) VALUES
('john_doe', 'john@', 'hashed_password_1'),
('jane_smith', 'jane@', 'hashed_password_2');
确保PHP中已启用相应的数据库扩展(如 `php_mysqli` 或 `php_pdo_mysql`)。您可以在 `` 文件中找到并取消注释 `extension=mysqli` 和 `extension=pdo_mysql`。
4. 使用 MySQLi 连接数据库
MySQLi 扩展提供了面向对象和面向过程两种接口。推荐使用面向对象的接口,因为它更符合现代编程范式。
4.1 MySQLi 面向对象连接示例
<?php
$servername = "localhost";
$username = "root"; // 您的数据库用户名
$password = "your_password"; // 您的数据库密码
$dbname = "mydatabase"; // 您的数据库名
// 创建连接
$conn = new mysqli($servername, $username, $password, $dbname);
// 检查连接
if ($conn->connect_error) {
die("连接失败: " . $conn->connect_error);
}
echo "<p>使用 MySQLi (面向对象) 数据库连接成功</p>";
// 在这里可以执行查询...
// 关闭连接
$conn->close();
?>
5. 使用 PDO 连接数据库
PDO 是推荐的连接方式,因为它支持多种数据库,并提供了统一的API。使用PDO连接数据库需要创建一个DSN (Data Source Name) 字符串。
5.1 PDO 连接示例
<?php
$dsn = "mysql:host=localhost;dbname=mydatabase;charset=utf8mb4";
$username = "root"; // 您的数据库用户名
$password = "your_password"; // 您的数据库密码
try {
// 创建PDO实例
$pdo = new PDO($dsn, $username, $password);
// 设置错误模式为异常,这样PDO会在出现错误时抛出PDOException
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 设置默认的查询结果集为关联数组
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
echo "<p>使用 PDO 数据库连接成功</p>";
// 在这里可以执行查询...
} catch (PDOException $e) {
die("连接失败: " . $e->getMessage());
}
// PDO连接在脚本执行完毕后会自动关闭,也可以显式设置为 null
// $pdo = null;
?>
6. 数据库查询操作:核心与安全
连接成功后,下一步就是执行SQL查询。这里是安全性最为关键的地方。请务必使用预处理语句(Prepared Statements)来防止SQL注入攻击。
6.1 什么是 SQL 注入?
SQL注入是一种常见的Web安全漏洞,攻击者通过在输入字段中插入恶意的SQL代码,来操纵应用程序的数据库查询,从而绕过认证、泄露敏感数据、甚至篡改或删除数据。例如,如果您的代码直接将用户输入拼接到SQL查询字符串中,攻击者可以输入 `admin' OR '1'='1` 来登录,或 `'; DROP TABLE users; --` 来删除表。
6.2 预处理语句(Prepared Statements)如何防范 SQL 注入?
预处理语句的工作原理是:首先发送带有占位符的SQL语句到数据库服务器进行编译,然后再将参数值单独发送给数据库。数据库服务器不会将参数值解释为SQL代码的一部分,而是作为纯粹的数据。这样就有效地阻止了SQL注入。
6.3 使用 MySQLi 预处理语句进行查询
6.3.1 查询数据 (SELECT)
<?php
// 假设 $conn 已经是一个成功的 MySQLi 连接
$search_username = "john_doe"; // 假设这是来自用户输入的查询条件
// 1. 准备SQL语句,使用问号作为占位符
$stmt = $conn->prepare("SELECT id, username, email, registration_date FROM users WHERE username = ?");
if ($stmt === false) {
die("预处理失败: " . $conn->error);
}
// 2. 绑定参数 (s代表字符串,i代表整数,d代表双精度浮点数,b代表blob)
$stmt->bind_param("s", $search_username);
// 3. 执行语句
$stmt->execute();
// 4. 获取结果集
$result = $stmt->get_result();
if ($result->num_rows > 0) {
// 遍历结果
while ($row = $result->fetch_assoc()) {
echo "<p>ID: " . $row["id"]. " - Username: " . $row["username"]. " - Email: " . $row["email"]. "</p>";
}
} else {
echo "<p>未找到用户。</p>";
}
// 5. 关闭语句
$stmt->close();
// 6. 关闭连接 (如果不再需要)
$conn->close();
?>
6.3.2 插入数据 (INSERT)
<?php
// 假设 $conn 已经是一个成功的 MySQLi 连接
$new_username = "bob_builder";
$new_email = "bob@";
$new_password_hash = password_hash("securepass", PASSWORD_DEFAULT); // 存储密码前务必哈希
$stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
if ($stmt === false) {
die("预处理失败: " . $conn->error);
}
$stmt->bind_param("sss", $new_username, $new_email, $new_password_hash);
if ($stmt->execute()) {
echo "<p>新用户插入成功,ID: " . $conn->insert_id . "</p>";
} else {
echo "<p>插入失败: " . $stmt->error . "</p>";
}
$stmt->close();
$conn->close();
?>
6.3.3 更新数据 (UPDATE)
<?php
// 假设 $conn 已经是一个成功的 MySQLi 连接
$user_id_to_update = 1;
$new_email_address = "@";
$stmt = $conn->prepare("UPDATE users SET email = ? WHERE id = ?");
if ($stmt === false) {
die("预处理失败: " . $conn->error);
}
$stmt->bind_param("si", $new_email_address, $user_id_to_update);
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo "<p>用户ID " . $user_id_to_update . " 的邮箱更新成功。</p>";
} else {
echo "<p>未找到用户ID " . $user_id_to_update . " 或邮箱未改变。</p>";
}
} else {
echo "<p>更新失败: " . $stmt->error . "</p>";
}
$stmt->close();
$conn->close();
?>
6.3.4 删除数据 (DELETE)
<?php
// 假设 $conn 已经是一个成功的 MySQLi 连接
$user_id_to_delete = 2;
$stmt = $conn->prepare("DELETE FROM users WHERE id = ?");
if ($stmt === false) {
die("预处理失败: " . $conn->error);
}
$stmt->bind_param("i", $user_id_to_delete);
if ($stmt->execute()) {
if ($stmt->affected_rows > 0) {
echo "<p>用户ID " . $user_id_to_delete . " 删除成功。</p>";
} else {
echo "<p>未找到用户ID " . $user_id_to_delete . "。</p>";
}
} else {
echo "<p>删除失败: " . $stmt->error . "</p>";
}
$stmt->close();
$conn->close();
?>
6.4 使用 PDO 预处理语句进行查询
6.4.1 查询数据 (SELECT)
<?php
// 假设 $pdo 已经是一个成功的 PDO 连接
$search_username = "john_doe"; // 假设这是来自用户输入的查询条件
try {
// 1. 准备SQL语句,使用命名占位符或问号占位符
$stmt = $pdo->prepare("SELECT id, username, email, registration_date FROM users WHERE username = :username");
// 2. 绑定参数
$stmt->bindParam(':username', $search_username, PDO::PARAM_STR);
// 或者使用 execute() 方法的数组参数
// $stmt->execute([':username' => $search_username]);
// 3. 执行语句
$stmt->execute();
// 4. 获取结果
if ($stmt->rowCount() > 0) {
while ($row = $stmt->fetch()) { // 默认 PDO::FETCH_ASSOC,因为在连接时已设置
echo "<p>ID: " . $row["id"]. " - Username: " . $row["username"]. " - Email: " . $row["email"]. "</p>";
}
} else {
echo "<p>未找到用户。</p>";
}
} catch (PDOException $e) {
echo "<p>查询失败: " . $e->getMessage() . "</p>";
}
// PDO连接在脚本执行完毕后会自动关闭
?>
PDO 的 `fetch()` 方法可以接受不同的参数来决定返回结果的格式:
PDO::FETCH_ASSOC: 返回一个关联数组。
PDO::FETCH_NUM: 返回一个索引数组。
PDO::FETCH_BOTH: 返回一个既有索引又有关联键的数组(默认)。
PDO::FETCH_OBJ: 返回一个匿名对象,其属性名为列名。
6.4.2 插入数据 (INSERT)
<?php
// 假设 $pdo 已经是一个成功的 PDO 连接
$new_username = "alice_wonder";
$new_email = "alice@";
$new_password_hash = password_hash("anotherpass", PASSWORD_DEFAULT);
try {
$stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (:username, :email, :password)");
// 使用 execute() 的数组参数绑定参数更简洁
$stmt->execute([
':username' => $new_username,
':email' => $new_email,
':password' => $new_password_hash
]);
echo "<p>新用户插入成功,ID: " . $pdo->lastInsertId() . "</p>";
} catch (PDOException $e) {
echo "<p>插入失败: " . $e->getMessage() . "</p>";
}
?>
6.4.3 更新数据 (UPDATE)
<?php
// 假设 $pdo 已经是一个成功的 PDO 连接
$user_id_to_update = 3;
$new_email_address = "@";
try {
$stmt = $pdo->prepare("UPDATE users SET email = :email WHERE id = :id");
$stmt->execute([
':email' => $new_email_address,
':id' => $user_id_to_update
]);
if ($stmt->rowCount() > 0) {
echo "<p>用户ID " . $user_id_to_update . " 的邮箱更新成功。</p>";
} else {
echo "<p>未找到用户ID " . $user_id_to_update . " 或邮箱未改变。</p>";
}
} catch (PDOException $e) {
echo "<p>更新失败: " . $e->getMessage() . "</p>";
}
?>
6.4.4 删除数据 (DELETE)
<?php
// 假设 $pdo 已经是一个成功的 PDO 连接
$user_id_to_delete = 1; // 假设 John Doe 用户的ID是1
try {
$stmt = $pdo->prepare("DELETE FROM users WHERE id = :id");
$stmt->execute([':id' => $user_id_to_delete]);
if ($stmt->rowCount() > 0) {
echo "<p>用户ID " . $user_id_to_delete . " 删除成功。</p>";
} else {
echo "<p>未找到用户ID " . $user_id_to_delete . "。</p>";
}
} catch (PDOException $e) {
echo "<p>删除失败: " . $e->getMessage() . "</p>";
}
?>
7. 高级实践与最佳建议
7.1 错误处理与日志
在生产环境中,不应该直接将数据库错误信息显示给用户。这可能会泄露敏感的数据库结构信息。应该捕获异常,记录到日志文件,然后向用户显示一个友好的错误消息。
// 示例:更健壮的PDO错误处理
try {
$pdo = new PDO($dsn, $username, $password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 启用异常模式
// ... 执行操作
} catch (PDOException $e) {
// 记录错误到日志文件
error_log("数据库错误: " . $e->getMessage() . " 在文件 " . $e->getFile() . " 第 " . $e->getLine() . " 行", 0);
// 向用户显示通用错误信息
echo "<p>抱歉,服务器发生了一个错误,请稍后再试。</p>";
// 或者重定向到错误页面
// header("Location: /");
// exit();
}
7.2 数据库连接管理
单例模式或依赖注入:在大型应用中,避免在每次需要数据库时都创建新的连接。可以使用单例模式来确保只有一个数据库连接实例,或者通过依赖注入将数据库连接传递给需要的对象。
连接池:对于高并发应用,可以考虑使用连接池技术,但这通常需要额外的配置和中间件。
7.3 配置管理
将数据库凭证(服务器名、用户名、密码、数据库名)存储在独立的配置文件中(例如 `` 或使用环境变量)。绝不能将数据库凭证硬编码在应用程序的核心逻辑文件中,更不能直接提交到版本控制系统(如Git)的公共仓库中。
//
<?php
return [
'db_host' => 'localhost',
'db_name' => 'mydatabase',
'db_user' => 'root',
'db_pass' => 'your_password',
'db_charset' => 'utf8mb4'
];
// 在应用程序中使用
$config = require '';
$dsn = "mysql:host={$config['db_host']};dbname={$config['db_name']};charset={$config['db_charset']}";
$pdo = new PDO($dsn, $config['db_user'], $config['db_pass']);
?>
7.4 事务处理 (Transactions)
对于需要执行一系列相互依赖的数据库操作(例如,银行转账,需要从一个账户扣款并向另一个账户加款),应该使用事务。事务确保所有操作要么全部成功(提交),要么全部失败(回滚),保持数据的一致性。
<?php
// 假设 $pdo 已经是一个成功的 PDO 连接
try {
$pdo->beginTransaction(); // 开启事务
// 操作1: 扣除用户A的余额
$stmt1 = $pdo->prepare("UPDATE accounts SET balance = balance - :amount WHERE user_id = :user_a");
$stmt1->execute([':amount' => 100, ':user_a' => 1]);
// 操作2: 增加用户B的余额
$stmt2 = $pdo->prepare("UPDATE accounts SET balance = balance + :amount WHERE user_id = :user_b");
$stmt2->execute([':amount' => 100, ':user_b' => 2]);
$pdo->commit(); // 提交事务,所有操作成功
echo "<p>转账成功!</p>";
} catch (PDOException $e) {
$pdo->rollBack(); // 回滚事务,撤销所有操作
error_log("转账失败: " . $e->getMessage());
echo "<p>转账失败,请联系管理员。</p>";
}
?>
8. 总结
通过本文的讲解,您应该已经掌握了PHP连接和查询MySQL数据库的关键技术。我们强调了选择现代数据库扩展(MySQLi或PDO),特别是PDO作为新项目首选的重要性。更重要的是,我们反复强调了使用预处理语句来防范SQL注入攻击的必要性,这是编写安全、健壮PHP应用程序的基石。结合良好的错误处理、配置管理和事务处理,您将能够构建出高效、安全且易于维护的Web应用程序。持续学习和实践是提升技能的最佳途径,希望本文能为您的PHP数据库编程之旅提供坚实的基础。---
2025-09-30
Python字符串查找与判断:从基础到高级的全方位指南
https://www.shuihudhg.cn/134118.html
C语言如何高效输出字符串“inc“?深度解析printf、puts及格式化输出
https://www.shuihudhg.cn/134117.html
PHP高效获取CSV文件行数:从小型文件到海量数据的最佳实践与性能优化
https://www.shuihudhg.cn/134116.html
C语言控制台图形输出:从入门到精通的ASCII艺术实践
https://www.shuihudhg.cn/134115.html
Python在Linux环境下的执行与自动化:从基础到高级实践
https://www.shuihudhg.cn/134114.html
热门文章
在 PHP 中有效获取关键词
https://www.shuihudhg.cn/19217.html
PHP 对象转换成数组的全面指南
https://www.shuihudhg.cn/75.html
PHP如何获取图片后缀
https://www.shuihudhg.cn/3070.html
将 PHP 字符串转换为整数
https://www.shuihudhg.cn/2852.html
PHP 连接数据库字符串:轻松建立数据库连接
https://www.shuihudhg.cn/1267.html