AI 摘要

文章系统讲解 PHP 操作 MySQL 的三种扩展:已淘汰的 mysql、专精 MySQL 的 mysqli 与跨库兼容的 PDO。重点演示 mysqli 的连接、字符集、查询、结果集遍历及预处理防注入,并给出订单表示例;随后详解 PDO 的 DSN 构建、异常模式、命名占位符预处理与增删改查完整流程,强调其现代框架首选地位与更高安全性。

PHP 操作数据库

PHP 中的数据库扩展

在 PHP 应用中要和 MySQL 数据库进行交互,需要借助 PHP 提供的数据库扩展。

  • MySQL 扩展:PHP 与 MySQL 数据库交互的早期扩展,在 PHP 7 中已经彻底被淘汰。
  • MySQLi 扩展:专为 MySQL 设计,是 MySQL 扩展的增强版,包含所有 MySQL 扩展的功能函数和 MySQL 高级特性。
  • PDO 扩展:解决了早期 PHP 中不同数据库扩展的 API 接口互不兼容的问题,是现代 PHP 框架和需要跨数据库的应用首选。

MySQLi

MySQLi 概述

MySQLi 扩展提供了大量的函数操作 MySQL,使得在 PHP 程序中操作数据库变得轻松便捷。

MySQLi 扩展默认已经安装,在使用时需要在 PHP 配置文件(php.ini)中开启。

PHP 配置文件(php.ini):

extension=mysqli

MySQLi 函数

MySQLi 扩展不仅为 PHP 连接数据库、执行 SQL 语句提供了函数,而且提供了很多简化开发的其他常用操作函数。

MySQLi 扩展的基本函数:

名称描述
mysqli_connect()连接 MySQL 服务器
mysqli_connect_error()获取连接服务器时的错误信息
mysqli_select_db()选择数据库
mysqli_set_charset()设置客户端字符集
mysqli_query()执行 SQL 语句
mysqli_insert_id()获取上一次插入操作时产生的 id
mysqli_affected_rows()获取上一次操作时受影响的行数
mysqli_errno()返回上一个 MySQL 操作中的错误信息的错误码
mysqli_error()返回上一个 MySQL 操作产生的错误信息
mysqli_close()关闭数据库连接

连接数据库

PHP 访问 MySQL 数据库之前,需要连接数据库。

mysqli_connect 函数的基本用法:

$数据库连接对象 = mysqli_connect(主机地址, 用户名, 密码, 数据库名, 主机端口号);

在使用 PHP 连接数据库时,还需要设置字符集,确保 PHP 与 MySQL 的连接使用相同的字符集 UTF8。

mysqli_set_charset 函数的基本用法:

mysqli_set_charset(数据库连接对象, 字符集编码);

MySQL 连接成功后,执行完操作,需要在适当的时候关闭数据库连接。

mysqli_close 函数的基本用法:

mysqli_close(数据库连接对象);

示例:连接数据库

SQL 脚本:

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for order
-- ----------------------------
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `orderId` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '订单编号',
  `productName` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '产品名称',
  `price` decimal(10,2) DEFAULT NULL COMMENT '产品价格',
  `userName` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '买家姓名',
  `phone` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '买家电话',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

-- ----------------------------
-- Records of order
-- ----------------------------
BEGIN;
INSERT INTO `order` (`id`, `orderId`, `productName`, `price`, `userName`, `phone`) VALUES (1, 'No.001', '智能手机', 14999.00, '张三', '13888888888');
INSERT INTO `order` (`id`, `orderId`, `productName`, `price`, `userName`, `phone`) VALUES (2, 'No.002', '笔记本电脑', 8999.00, '李四', '15788888888');
INSERT INTO `order` (`id`, `orderId`, `productName`, `price`, `userName`, `phone`) VALUES (3, 'No.003', '无线蓝牙耳机', 129.00, '王五', '18988888888');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

入口页面(index.php):

<?php
// 连接数据库
$mysql = mysqli_connect("localhost", "root", "123456..", "demo", "3306");

if(!$mysql) {
    // 连接失败,输出错误信息
    exit(mysqli_error($mysql));
}

// 设置字符集编码
mysqli_set_charset($mysql, "utf8");

print_r("数据库连接成功");

// 关闭数据库连接
mysqli_close($mysql);

示例效果:


执行 SQL 语句

数据库连接成功后,就可以执行 SQL 语句进行数据操作。

mysqli_query 函数的基本用法:

mysqli_query(数据库连接对象, SQL语句, 结果集模式);

当函数执行写操作时,函数返回值为 boolean。

当函数执行读操作时,函数根据结果集模式返回查询结果集。

  • MYSQLI_STORE_RESULT:默认模式,会将结果集全部读取到 PHP。
  • MYSQLI_USE_RESULT:仅初始化结果集检索,在处理结果集时进行数据读取。

处理结果集

使用 mysqli_query 函数执行读操作时,返回的是一个资源类型的结果集,需要对结果集作进一步处理,获取结果集中的数据。

MySQLi 扩展处理结果集的基本函数:

名称描述
mysqli_num_rows(结果集对象)获取结果中行的数量
mysqli_fetch_all(结果集对象, 数组形式)获取所有结果并以数组方式返回
数组形式:MYSQL_ASSOC(关联数组)/ MYSQL_NUM(索引数组)
mysqli_fetch_array(结果集对象)获取一行结果并以数组方式返回
mysqli_fetch_assoc(结果集对象)获取一行结果并以关联数组返回
mysqli_fetch_row(结果集对象)获取一行结果并以索引数组返回
mysqli_free_result(结果集对象)释放结果集

示例:处理结果集

入口页面(index.php):

<?php
// 连接数据库
$mysql = mysqli_connect("localhost", "root", "123456..", "demo", "3306");

if(!$mysql) {
    // 连接失败,输出错误信息
    exit(mysqli_error($mysql));
}

// 设置字符集编码
mysqli_set_charset($mysql, "utf8");

print_r("数据库连接成功");

// 执行SQL语句
$result = mysqli_query($mysql, "SELECT * FROM `order`");

// 获取结果集数组
print_r("<pre>");
print_r(mysqli_fetch_all($result, MYSQLI_ASSOC));
print_r("</pre>");

// 关闭数据库连接
mysqli_close($mysql);

示例效果:


预处理操作

使用 MySQLi 扩展执行增删改操作时,需要拼接 SQL 语句,效率低,安全性差,容易导致 SQL 注入问题。

MySQLi 扩展预处理提供了预处理方式,实现 SQL 语句和数据的分离,执行 SQL 语句运行效率高,无须考虑数据中包含特殊字符导致的 SQL 注入问题。

预处理的实现过程:

  • 定义一个 SQL 语句模板。
  • 为该模板进行参数绑定。
  • 将用户提交的数据内容发送给 MySQL 执行。

mysqli_prepare 函数用于预处理 SQL 模板,在编写 SQL 模板时,使用 ? 号占位符代替数据部分,且占位符两边无须使用引号包裹。

mysqli_prepare 函数的基本用法:

$预处理对象 = mysqli_prepare(数据库连接对象, SQL模板);

mysqli_stmt_bind_param 函数用于将变量作为参数绑定到预处理语句中。

mysqli_stmt_bind_param 函数的基本用法:

mysqli_stmt_bind_param(预处理对象, 数据类型, 绑定变量1, 绑定变量2...);

使用 mysqli_stmt_bind_param 函数进行参数绑定时,可以指定数据类型,且与绑定变量一一对应,绑定变量使用引用传参。

参数绑定时可以指定的基本数据类型:

名称描述
i整型
d浮点型
s字符串型
b二进制对象

mysqli_stmt_execute 函数用于执行预处理语句。

mysqli_stmt_execute 函数的基本用法:

mysqli_stmt_execute(预处理对象);

示例:预处理操作

入口页面(index.php):

<?php
// 连接数据库
$mysql = mysqli_connect("localhost", "root", "123456..", "demo", "3306");

if(!$mysql) {
    // 连接失败,输出错误信息
    exit(mysqli_error($mysql));
}

// 设置字符集编码
mysqli_set_charset($mysql, "utf8");

print_r("数据库连接成功");

// 定义预处理对象
$stmt = mysqli_prepare($mysql, "UPDATE `order` SET productName = ?, price = ? WHERE id = ?");

// 绑定参数
$productName = "HUAWEI手机";
$price = 7999.0;
$id = 1;
mysqli_stmt_bind_param($stmt, "sdi", $productName, $price, $id);

// 执行预处理语句
$result = mysqli_stmt_execute($stmt);

print_r($result ? "执行成功" : "执行失败");

// 关闭数据库连接
mysqli_close($mysql);

示例效果:


PDO

PDO 概述

PDO(PHP Data Objects)是 PHP 官方提供的数据库抽象层扩展,从 PHP 5.1 开始内置支持,PHP 7/8 成为操作数据库的主流标准,也是所有主流 PHP 框架(如 Laravel、ThinkPHP、Yii 等)的底层数据库驱动。

PDO 是一套统一的数据库操作接口,封装了不同数据库的底层实现差异,让开发者能用同一套代码语法操作 MySQL、PostgreSQL、SQLite、Oracle 等几十种数据库,同时提供了完善的防 SQL 注入方案,是现代 PHP 开发中操作数据库的首选方案。

PDO 与 mysqli、mysqli 对比:

特性PDOmysqlimysql
支持数据库跨库兼容仅 MySQL/MariaDB仅 MySQL/MariaDB
防注入支持原生预处理(推荐)原生预处理(仅问号占位)无,需手动转义
占位符类型命名占位符 + 问号占位符仅问号占位符无占位符,需拼接 SQL
错误处理支持异常抛出,高效需手动判断,繁琐需手动判断,繁琐
语法风格统一面向对象语法面向对象 + 过程化(双套)仅过程化语法

PHP 7/8 已默认启用 PDO,无需额外安装,如未默认启用,需要在 PHP 配置文件(php.ini)中开启。

PHP 配置文件(php.ini):

extension=pdo_mysql

连接数据库

PDO 通过构造函数创建连接对象。

PDO 构造函数的基本用法:

// 构建 DSN
$dsn = "mysql:host=主机地址:主机端口号;dbname=数据库名;charset=编码";

// 构建 PDO核心配置项
$options = [
    // 错误时抛出异常,替代手动判断
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    // 关闭模拟预处理,使用MySQL原生预处理,防SQL注入核心
    PDO::ATTR_EMULATE_PREPARES => false,
    // 默认返回关联数组
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];

try {
    // 构建 PDO
    $pdo = new PDO($dsn, 用户名, 密码, $options);
} catch (PDOException $e) {
    // 捕获异常
    die("数据库连接失败:".$e->getMessage());
}

示例:连接数据库

入口页面(index.php):

<?php
$dsn = "mysql:host=localhost:3306;dbname=demo;charset=utf8mb4";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];

try {
    $pdo = new PDO($dsn, "root", "123456..", $options);
    echo "数据库连接成功";
} catch (PDOException $e) {
    die("数据库连接失败:".$e->getMessage());
}

示例效果:


预处理操作

PDO 执行 SQL 分两种场景:

  • 所有带动态参数的 SQL(如用户输入、变量),必须用「预处理语句」,这是防 SQL 注入的唯一有效方式。
  • 无参数的固定 SQL(如查所有数据),可直接执行。

预处理语句分为 prepare 预处理 和 execute 执行两步:

  • 先将带占位符的 SQL 发送给 MySQL 编译,编译后可重复执行。
  • 再将参数值发送给 MySQL,参数值会被当作纯数据处理,不会被解析为 SQL 语句,从根本上避免注入。

PDO 支持两种占位符:

  • 命名占位符::变量名(如:id、:name),执行时用关联数组传参,推荐使用命名占位符。
  • 问号占位符:?,执行时用索引数组传参,参数顺序必须和 SQL 中一致。

预处理操作的基本用法:

// 定义带命名占位符的SQL(字段名和占位符对应)
$sql = "INSERT INTO user (name, age, email) VALUES (:name, :age, :email)";

// prepare 预处理
$stmt = $pdo->prepare($sql);

// 定义参数数组(键=占位符,值=实际数据)
$params = [
    ':name' => '张三',
    ':age' => 20,
    ':email' => 'zhangsan@test.com'
];

// execute 执行
$stmt->execute($params);

CRUD

PDO CRUD 中的基本函数:

名称描述
lastInsertId()获取新增数据的自增 ID
fetch()获取单条数据,适用于按 ID 查、查一条记录
fetchAll()获取所有数据,适用于查列表、多条记录
rowCount()获取查询结果的行数
增删改时返回操作影响的行数

PDO 操作数据库的核心流程:

  • 创建 PDO 连接对象:配置数据库地址、库名、账号密码及核心参数,建立与数据库的连接。PDO 对象全局唯一,一个项目只需一个连接,可设计为单例。
  • 预处理 SQL 语句:通过 prepare 函数编译带占位符的 SQL,返回语句对象。
  • 执行并传参:通过 execute 函数传递参数,执行预处理后的 SQL。
  • 获取执行结果:通过 CRUD 基本半数获取执行结果。

示例:CRUD

入口页面(index.php):

<?php
$dsn = "mysql:host=localhost:3306;dbname=demo;charset=utf8mb4";
$options = [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];

try {
    $pdo = new PDO($dsn, "root", "123456..", $options);
    echo "数据库连接成功";
} catch (PDOException $e) {
    die("数据库连接失败:".$e->getMessage());
}

// 新增数据
echo "【新增数据】<br>";
$insertSql = "INSERT INTO `order` (orderId, productName, price, userName, phone) VALUES (:orderId, :productName, :price, :userName, :phone)";
$insertStmt = $pdo->prepare($insertSql);
$insertStmt->execute([
    ':orderId' => 'No.004',
    ':productName' => '华为Mate80 Pro',
    ':price' => 9999.00,
    ':userName' => '张三',
    ':phone' => '13800138000'
]);
$newOrderId = $pdo->lastInsertId();
echo "新增成功!自增ID:{$newOrderId}<br><hr>";

// 查询数据
echo "【查询数据】<br>";
$listSql = "SELECT id, orderId, productName, price, userName FROM `order` ORDER BY id DESC";
$listStmt = $pdo->query($listSql);
$orderList = $listStmt->fetchAll();
echo "所有数据(共{$listStmt->rowCount()}条):<br>";
foreach ($orderList as $item) {
    echo "ID:{$item['id']} | 订单号:{$item['orderId']} | 商品:{$item['productName']} | 价格:{$item['price']}元<br>";
}
echo "<hr>";

// 修改数据
echo "【修改数据】<br>";
$updateSql = "UPDATE `order` SET productName = :productName, price = :price, phone = :phone WHERE id = :id";
$updateStmt = $pdo->prepare($updateSql);
$updateStmt->execute([
    ':productName' => '华为Mate60 Pro+',
    ':price' => 7999.00,
    ':phone' => '13900139000',
    ':id' => $newOrderId
]);
$updateRow = $updateStmt->rowCount();
if ($updateRow > 0) {
    echo "修改成功!共修改{$updateRow}条数据<br>";
} else {
    echo "修改失败<br>";
}
echo "<hr>";

echo "【删除数据】<br>";
$deleteSql = "DELETE FROM `order` WHERE id = :id";
$deleteStmt = $pdo->prepare($deleteSql);
$deleteStmt->execute([':id' => 1]);
$deleteRow = $deleteStmt->rowCount();
if ($deleteRow > 0) {
    echo "删除成功!已删除ID为{$newOrderId}的数据<br>";
} else {
    echo "删除失败<br>";
}

示例效果: