PHP 链接数据库与基础增删改查(CRUD)操作详解
一、环境准备
1.1 核心环境与工具
PHP 环境:推荐 PHP 7.4+ 版本(本文使用 PHP 8.1,兼容 MySQL 8.0+,性能更优)。
MySQL 数据库:推荐 MySQL 8.0 或 MariaDB 10.5+,需提前创建测试库和表。
开发工具:Visual Studio Code(配 PHP 插件)、PhpStorm,或简易编辑器如 Sublime Text。
运行服务器:XAMPP、WAMP(集成 Apache + PHP + MySQL,新手首选),或独立配置 Nginx + PHP-FPM。
数据库管理工具:Navicat、SQLyog 或 PHPMyAdmin(XAMPP 自带),用于执行 SQL 语句和管理数据。
1.2 环境验证
启动 PHP 运行环境(如 XAMPP 启动 Apache 和 MySQL)。
在网站根目录(如 XAMPP 的
htdocs文件夹)创建phpinfo.php,内容如下:php运行<?phpphpinfo(); // 输出 PHP 环境信息?>
浏览器访问
http://localhost/phpinfo.php,搜索mysqli或PDO,确认扩展已启用(默认 PHP 7+ 已内置,无需额外安装)。
1.3 测试数据库与表创建
php_db 和用户表 users:-- 1. 创建数据库(若不存在),指定 UTF-8 编码CREATE DATABASE IF NOT EXISTS php_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;-- 2. 使用数据库USE php_db;-- 3. 创建用户表(存储用户ID、姓名、年龄、邮箱、注册时间)CREATE TABLE IF NOT EXISTS users ( id INT PRIMARY KEY AUTO_INCREMENT, -- 主键自增 name VARCHAR(50) NOT NULL, -- 姓名,非空 age TINYINT UNSIGNED, -- 年龄,无符号(0-255) email VARCHAR(100) UNIQUE NOT NULL,-- 邮箱,唯一且非空 create_time DATETIME DEFAULT CURRENT_TIMESTAMP -- 注册时间,默认当前时间) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、PHP 链接数据库的两种核心方式
MySQLi(MySQL 改进版扩展,仅支持 MySQL)和 PDO(PHP Data Objects,支持多数据库如 MySQL、PostgreSQL 等)。本文重点讲解 PDO(通用性强、支持预处理防 SQL 注入,推荐生产环境使用),同时补充 MySQLi 基础用法作为对比。2.1 核心连接参数
数据库地址:
localhost(本地)或远程服务器 IP。端口号:MySQL 默认 3306(若未修改)。
数据库名:
php_db(上文创建的测试库)。用户名 / 密码:你的 MySQL 登录信息(如 root/123456)。
2.2 PDO 连接方式(推荐)
<?php/**
* PDO 数据库工具类:封装连接、关闭、异常处理
*/class PDODB {
// 数据库连接参数(实际开发建议放在配置文件中)
private static $host = 'localhost';
private static $dbname = 'php_db';
private static $username = 'root';
private static $password = '123456';
private static $charset = 'utf8mb4';
private static $dsn; // 数据源名称(PDO 连接格式)
private static $pdo = null; // PDO 实例(单例模式,避免重复创建连接)
/**
* 初始化连接(单例模式)
* @return PDO 连接实例
* @throws PDOException 连接异常
*/
public static function getConnection() {
// 若已创建连接,直接返回
if (self::$pdo !== null) {
return self::$pdo;
}
// 构建 DSN(PDO 连接格式:mysql:host=xxx;dbname=xxx;charset=xxx)
self::$dsn = "mysql:host=" . self::$host . ";dbname=" . self::$dbname . ";charset=" . self::$charset;
try {
// 创建 PDO 实例(开启异常模式,便于捕获错误)
self::$pdo = new PDO(
self::$dsn,
self::$username,
self::$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // 抛出异常
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC // 默认以关联数组返回结果
]
);
echo "PDO 数据库连接成功!<br>";
return self::$pdo;
} catch (PDOException $e) {
// 连接失败,抛出异常(实际开发可记录日志)
die("PDO 连接失败:" . $e->getMessage());
}
}
/**
* 关闭连接(PDO 会自动关闭,手动关闭仅作演示)
*/
public static function closeConnection() {
self::$pdo = null;
echo "PDO 连接已关闭!<br>";
}}关键说明:
ERRMODE_EXCEPTION:开启异常模式,后续 SQL 错误会抛出异常,便于统一处理。
FETCH_ASSOC:默认以关联数组返回查询结果(如['id' => 1, 'name' => '张三']),使用更直观。单例模式:避免重复创建数据库连接,减少服务器资源消耗。
2.3 MySQLi 连接方式(补充)
<?php// MySQLi 面向对象连接$host = 'localhost';$dbname = 'php_db';$username = 'root';$password = '123456';// 创建 MySQLi 实例$mysqli = new mysqli($host, $username, $password, $dbname);// 检查连接错误if ($mysqli->connect_error) {
die("MySQLi 连接失败:" . $mysqli->connect_error);}// 设置字符编码$mysqli->set_charset('utf8mb4');echo "MySQLi 连接成功!<br>";// 关闭连接$mysqli->close();echo "MySQLi 连接已关闭!<br>";三、基础增删改查(CRUD)操作实现
users 表的 CRUD 操作,核心使用 PDO::prepare() 预处理语句(防 SQL 注入,生产环境必备)。3.1 新增操作(Create)
users 表插入一条新用户数据。<?phprequire_once 'PDODB.php'; // 引入 PDO 工具类try {
// 1. 获取数据库连接
$pdo = PDODB::getConnection();
// 2. 定义 SQL(:name 为命名占位符,也可使用 ? 问号占位符)
$sql = "INSERT INTO users (name, age, email) VALUES (:name, :age, :email)";
// 3. 预处理 SQL(编译 SQL,避免重复解析,提升性能)
$stmt = $pdo->prepare($sql);
// 4. 绑定参数并执行(两种方式可选)
// 方式1:数组绑定(推荐,简洁)
$userData = [
':name' => '张三',
':age' => 24,
':email' => 'zhangsan@example.com'
];
$stmt->execute($userData);
// 方式2:逐个绑定(适合需要指定参数类型的场景)
// $stmt->bindParam(':name', $name, PDO::PARAM_STR);
// $stmt->bindParam(':age', $age, PDO::PARAM_INT);
// $stmt->execute();
// 获取新增数据的自增 ID
$newUserId = $pdo->lastInsertId();
echo "新增用户成功!用户ID:" . $newUserId . "<br>";} catch (PDOException $e) {
echo "新增失败:" . $e->getMessage() . "<br>";} finally {
// 关闭连接(可选,PDO 脚本结束会自动关闭)
PDODB::closeConnection();}3.2 查询操作(Read)
fetch()(单条)或 fetchAll()(多条)处理结果集。3.2.1 查询单条数据(按 ID 查询)
<?phprequire_once 'PDODB.php';try {
$pdo = PDODB::getConnection();
$targetId = 1; // 要查询的用户ID
// 预处理查询 SQL
$sql = "SELECT id, name, age, email, create_time FROM users WHERE id = :id";
$stmt = $pdo->prepare($sql);
// 执行查询(绑定参数)
$stmt->execute([':id' => $targetId]);
// 获取单条结果(关联数组格式)
$user = $stmt->fetch();
if ($user) {
echo "查询到用户:<br>";
echo "ID:" . $user['id'] . "<br>";
echo "姓名:" . $user['name'] . "<br>";
echo "年龄:" . $user['age'] . "<br>";
echo "邮箱:" . $user['email'] . "<br>";
echo "注册时间:" . $user['create_time'] . "<br>";
} else {
echo "未查询到 ID 为 " . $targetId . " 的用户<br>";
}} catch (PDOException $e) {
echo "查询失败:" . $e->getMessage() . "<br>";} finally {
PDODB::closeConnection();}3.2.2 查询多条数据(查询所有用户)
fetchAll() 获取所有数据:<?phprequire_once 'PDODB.php';try {
$pdo = PDODB::getConnection();
// 查询所有用户(按注册时间倒序)
$sql = "SELECT id, name, age, email FROM users ORDER BY create_time DESC";
$stmt = $pdo->prepare($sql);
$stmt->execute();
// 获取所有结果(关联数组集合)
$users = $stmt->fetchAll();
if (count($users) > 0) {
echo "所有用户列表:<br>";
foreach ($users as $index => $user) {
echo "序号 " . ($index + 1) . ":";
echo "ID=" . $user['id'] . ",姓名=" . $user['name'] . ",邮箱=" . $user['email'] . "<br>";
}
} else {
echo "暂无用户数据<br>";
}} catch (PDOException $e) {
echo "查询失败:" . $e->getMessage() . "<br>";} finally {
PDODB::closeConnection();}3.3 修改操作(Update)
<?phprequire_once 'PDODB.php';try {
$pdo = PDODB::getConnection();
$userId = 1; // 要修改的用户ID
// 定义修改 SQL
$sql = "UPDATE users SET name = :name, age = :age WHERE id = :id";
$stmt = $pdo->prepare($sql);
// 绑定参数并执行
$updateData = [
':name' => '张三_更新',
':age' => 25,
':id' => $userId
];
$stmt->execute($updateData);
// 获取受影响的行数
$affectedRows = $stmt->rowCount();
if ($affectedRows > 0) {
echo "修改成功!受影响行数:" . $affectedRows . "<br>";
} else {
echo "修改失败:未找到用户或数据未变更<br>";
}} catch (PDOException $e) {
echo "修改失败:" . $e->getMessage() . "<br>";} finally {
PDODB::closeConnection();}3.4 删除操作(Delete)
<?phprequire_once 'PDODB.php';try {
$pdo = PDODB::getConnection();
$userId = 1; // 要删除的用户ID
// 定义删除 SQL
$sql = "DELETE FROM users WHERE id = :id";
$stmt = $pdo->prepare($sql);
// 执行删除
$stmt->execute([':id' => $userId]);
$affectedRows = $stmt->rowCount();
if ($affectedRows > 0) {
echo "删除成功!已删除 ID 为 " . $userId . " 的用户<br>";
} else {
echo "删除失败:未找到该用户<br>";
}} catch (PDOException $e) {
echo "删除失败:" . $e->getMessage() . "<br>";} finally {
PDODB::closeConnection();}四、关键注意事项与安全优化
4.1 防 SQL 注入(重中之重)
使用预处理语句:无论 PDO 还是 MySQLi,都必须使用
prepare()+execute()绑定参数,严禁直接拼接 SQL 字符串(如下错误示范):php运行// 错误示范:直接拼接参数,存在 SQL 注入风险$sql = "SELECT * FROM users WHERE name = '" . $_GET['name'] . "'";
过滤用户输入:对用户提交的参数(如表单、URL 参数)进行过滤,例如使用
trim()去除空格、htmlspecialchars()转义特殊字符。
4.2 常见问题排查
- 连接失败:
检查 MySQL 是否启动,端口号(3306)是否被占用。
确认用户名、密码正确,且该用户有权限访问
php_db数据库。若 MySQL 8.0+ 报 “认证方式错误”,需修改 MySQL 认证规则(参考:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';)。- 中文乱码:
数据库、表、字段的编码必须统一为
utf8mb4(支持 emoji 表情)。PHP 连接时需指定
charset=utf8mb4(PDO 已在 DSN 中配置)。网页输出时设置编码:
header("Content-Type: text/html; charset=utf-8");。- 预处理语句执行失败:
检查占位符名称(如
:name)与绑定参数的键名是否一致。确认字段类型与参数类型匹配(如
age是 int 类型,避免传入字符串)。
4.3 生产环境优化建议
- 配置文件分离:将数据库连接参数(host、username、password)放在独立的配置文件(如
config.php)中,且该文件放在网站根目录外(避免被直接访问):php运行// config.php(放在 htdocs 外)return [ 'host' => 'localhost', 'dbname' => 'php_db', 'username' => 'prod_user', // 生产环境使用低权限用户 'password' => 'strong_password' // 复杂密码,避免明文];
引入方式:$config = require '/path/to/config.php';。 - 错误处理优化:生产环境禁用
die()直接输出错误信息(避免泄露敏感信息),改为记录日志(如使用error_log()或日志框架):php运行catch (PDOException $e) { error_log("数据库错误:" . $e->getMessage(), 3, '/path/to/error.log'); // 写入日志 echo "系统繁忙,请稍后再试!"; // 向用户显示友好提示} - 使用数据库连接池:高并发场景下,频繁创建 / 关闭数据库连接会消耗大量资源,可使用连接池工具(如
php-pdo-pool)复用连接,提升性能。 - 限制数据库用户权限:生产环境中,PHP 连接数据库的用户仅授予
SELECT、INSERT、UPDATE、DELETE等必要权限,禁止授予DROP、ALTER等高危权限。
五、总结
环境准备:确保 PHP 环境启用 PDO 扩展,创建测试数据库和表。
连接方式:推荐使用 PDO(通用性强、安全),封装工具类统一管理连接。
CRUD 实现:基于 PDO 预处理语句实现新增、查询、修改、删除,避免 SQL 注入。
安全与优化:分离配置文件、记录错误日志、限制用户权限,适配生产环境。