PHP MySQL 数据库
PHP 与 MySQL 是经典组合,通过 MySQLi 或 PDO 扩展进行数据库操作。MySQLi(MySQL Improved)是专为 MySQL 设计的扩展,提供了面向对象和过程式两种 API,支持预处理语句、事务、多语句执行等特性。本文重点介绍面向对象方式,这也是现代 PHP 项目的推荐做法。
连接数据库(MySQLi)
建立数据库连接是所有数据库操作的第一步。MySQLi 的面向对象方式通过 new mysqli() 创建连接对象,连接后应立即设置字符集为 utf8mb4,以支持完整的 Unicode 字符(包括 emoji)。
$host = 'localhost'; $user = 'root'; $pass = 'your_password'; $dbname = 'myapp';vlucas/phpdotenv$conn = new mysqli($host, $user, $pass, $dbname); $conn->set_charset('utf8mb4');
if ($conn->connect_error) { die("连接失败:" . $conn->connect_error); }
echo "连接成功"; ``
生产环境中,数据库连接信息不应硬编码在代码中,应通过环境变量或配置文件读取。推荐使用
库读取.env文件,并将.env加入.gitignore,防止敏感信息泄露到版本控制系统。`创建数据表
在操作数据之前,需要先设计并创建数据表。良好的表结构设计是数据库性能和数据完整性的基础。
sql 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, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );`password字段长度设为 255 是因为password_hash()使用 bcrypt 算法生成的哈希值长度约为 60 字符,但未来可能使用更长的算法,预留足够空间是好习惯。bind_param()增删改查(CRUD)
插入数据
永远不要用字符串拼接构造 SQL 语句,这会导致 SQL 注入漏洞。预处理语句(Prepared Statements)是防止 SQL 注入的标准方法,参数通过
绑定,与 SQL 语句分离传输。`php $stmt = $conn->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $username, $email, $hashed);`$username = "张三"; $email = "zhangsan@example.com"; $hashed = password_hash("123456", PASSWORD_DEFAULT);
$stmt->execute(); echo "插入成功,ID:" . $conn->insert_id; $stmt->close();
query()查询数据
查询是最常见的数据库操作。对于不带用户输入的简单查询,可以直接用
方法;带参数的查询必须使用预处理语句。`php // 查询所有 $result = $conn->query("SELECT id, username, email FROM users"); while ($row = $result->fetch_assoc()) { echo "{$row['id']} - {$row['username']} - {$row['email']}\n"; }`// 带条件查询(预处理) $stmt = $conn->prepare("SELECT * FROM users WHERE id = ?"); $stmt->bind_param("i", $id); $id = 1; $stmt->execute(); $user = $stmt->get_result()->fetch_assoc(); echo $user['username']; $stmt->close();
`更新数据
php $stmt = $conn->prepare("UPDATE users SET email = ? WHERE id = ?"); $stmt->bind_param("si", $email, $id);`$email = "new@example.com"; $id = 1; $stmt->execute();
echo "影响行数:" . $stmt->affected_rows; $stmt->close();
`删除数据
php $stmt = $conn->prepare("DELETE FROM users WHERE id = ?"); $stmt->bind_param("i", $id); $id = 1; $stmt->execute(); echo "删除成功"; $stmt->close();`deleted_at执行删除操作前,建议先查询确认记录存在,并在业务层做权限验证,防止越权删除。对于重要数据,可以考虑软删除(添加
字段)而不是物理删除。`bind_param 类型说明
字符 类型 s string 字符串 i integer 整数 d double 浮点数 b blob 二进制 事务处理
事务保证了一组数据库操作的原子性——要么全部成功,要么全部回滚。这在涉及多表操作的业务场景(如转账、下单)中至关重要。
php $conn->begin_transaction();`try { $stmt = $conn->prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?"); $stmt->bind_param("di", $amount, $fromId); $amount = 500; $fromId = 1; $stmt->execute();
$stmt = $conn->prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?"); $stmt->bind_param("di", $amount, $toId); $toId = 2; $stmt->execute();
$conn->commit(); echo "转账成功"; } catch (Exception $e) { $conn->rollback(); echo "转账失败:" . $e->getMessage(); }
LIMIT分页查询
分页是列表页面的标配功能,通过
和OFFSET实现。需要同时查询总记录数来计算总页数。`php $page = max(1, (int)($_GET['page'] ?? 1)); $pageSize = 10; $offset = ($page - 1) * $pageSize;`$total = $conn->query("SELECT COUNT(*) FROM users")->fetch_row()[0]; $pages = ceil($total / $pageSize);
$stmt = $conn->prepare("SELECT * FROM users ORDER BY id DESC LIMIT ? OFFSET ?"); $stmt->bind_param("ii", $pageSize, $offset); $stmt->execute(); $users = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
foreach ($users as $user) { echo $user['username'] . "\n"; }
echo "第 $page / $pages 页,共 $total 条";
`关闭连接
php $conn->close();
PHP 脚本结束时会自动关闭数据库连接,但在长时间运行的脚本(如 CLI 任务)中,应在不再需要连接时主动关闭,释放数据库服务器的连接资源。
常见问题
Q:MySQLi 和 PDO 应该选哪个? A:对于只使用 MySQL 的项目,两者都可以。PDO 的优势在于支持多种数据库,命名参数(:name)比 MySQLi 的 ? 占位符更清晰,异常处理也更统一。MySQLi 的优势是对 MySQL 特性支持更完整。新项目推荐使用 PDO,尤其是可能需要切换数据库的场景。
Q:预处理语句真的能完全防止 SQL 注入吗? A:正确使用预处理语句可以防止 SQL 注入,因为参数值与 SQL 语句分离传输。但有几点注意:表名和列名不能用预处理语句参数化,如果需要动态表名/列名,必须用白名单验证;预处理语句不能防止业务逻辑漏洞(如越权访问)。
Q:如何处理 MySQLi 的错误,避免程序崩溃? A:推荐在连接时开启异常模式:mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT),这样 MySQLi 操作失败时会自动抛出 mysqli_sql_exception,可以用 try/catch 统一处理。
Q:数据库连接应该在哪里创建,如何避免重复连接? A:推荐使用单例模式或依赖注入容器管理数据库连接,确保整个请求生命周期内只创建一个连接。最简单的方式是用静态变量:static $conn = null; if (!$conn) { $conn = new mysqli(...); }`。