老早之前PHP有个mysql_query()函数,能直接删数据。但这东西PHP 5.5开始就被标记为过时了,后续版本直接给踢出去了。你要是还在用这个,要么项目是十年前的祖传代码,要么就是刚入坑还不知道情况。
现在正经写PHP删除操作,就两条路:MySQLi和PDO。MySQLi分面向过程和面向对象两种写法,PDO更适合需要换数据库的项目。
DELETE语句长啥样
DELETE FROM 表名 WHERE 条件;
这里有个坑必须说清楚:不带WHERE就是把整个表清空。我见过有新手在正式环境跑了个DELETE FROM users,忘记加条件,那场面简直惨不忍睹。生产库没备份的话,收拾东西走人都是轻的。
MySQLi面向过程写法
这是最直接的写法,适合写小脚本或者快速测试用。
<?php
$host = 'localhost:3306';
$user = 'root';
$pass = '123456';
$dbname = 'company_db';
$conn = mysqli_connect($host, $user, $pass, $dbname);
if (!$conn) {
die('连不上数据库:' . mysqli_connect_error());
}
// 删除id为5的员工
$id = 5;
$sql = "DELETE FROM employees WHERE id = $id";
if (mysqli_query($conn, $sql)) {
echo "员工记录已删除";
} else {
echo "删除失败:" . mysqli_error($conn);
}
mysqli_close($conn);
?>
动手之前看一眼数据:
| id | name | department |
|---|---|---|
| 1 | 张三 | 技术部 |
| 2 | 李四 | 市场部 |
| 3 | 王五 | 部 |
| 4 | 赵六 | 技术部 |
| 5 | 老钱 | 财务部 |
执行之后:
| id | name | department |
|---|---|---|
| 1 | 张三 | 技术部 |
| 2 | 李四 | 市场部 |
| 3 | 王五 | 部 |
| 4 | 赵六 | 技术部 |
id=5的那条没了。
个人经验: 这种直接把变量拼进SQL的做法,如果$id是从用户输入来的(比如GET参数),那SQL注入风险很高。上面只是演示,实际项目中别这么干,往下看预处理就明白了。
MySQLi面向对象写法
<?php
$mysqli = new mysqli("localhost", "root", "123456", "company_db");
if ($mysqli->connect_error) {
die("连接失败:" . $mysqli->connect_error);
}
$sql = "DELETE FROM employees WHERE id = 3";
if ($mysqli->query($sql) === true) {
echo "id为3的记录已删除";
} else {
echo "删除出错:" . $mysqli->error;
}
$mysqli->close();
?>
处理前数据:
| id | name | salary |
|---|---|---|
| 1 | 张三 | 8000 |
| 2 | 李四 | 9500 |
| 3 | 王五 | 7000 |
| 4 | 赵六 | 11000 |
处理后数据:
| id | name | salary |
|---|---|---|
| 1 | 张三 | 8000 |
| 2 | 李四 | 9500 |
| 4 | 赵六 | 11000 |
说一下区别: 面向过程和面向对象本质上都是MySQLi扩展,只不过写法习惯不同。团队项目里面向对象更常见,因为和后面要说的PDO风格接近,切换成本低。
PDO方式删除
PDO的好处是换个数据库不用改太多代码,比如从MySQL切到PostgreSQL,连接字符串改一下就行。
<?php
try {
$pdo = new PDO("mysql:host=localhost;dbname=company_db", "root", "123456");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "DELETE FROM employees WHERE id = 2";
$pdo->exec($sql);
echo "删除成功";
} catch (PDOException $e) {
die("执行失败:" . $e->getMessage());
}
unset($pdo);
?>
注意: exec()用来执行没有结果集返回的SQL,比如DELETE、UPDATE、INSERT。如果执行SELECT用exec会报错。
预处理语句(重点)
上面几个例子有个共同毛病:SQL里直接拼变量。这习惯必须改。
MySQLi预处理
<?php
$conn = new mysqli("localhost", "root", "123456", "company_db");
if ($conn->connect_error) {
die("连不上:" . $conn->connect_error);
}
// 要删除的用户ID,这个值可以来自表单、URL参数等
$user_id = 7;
$stmt = $conn->prepare("DELETE FROM employees WHERE id = ?");
$stmt->bind_param("i", $user_id); // i表示integer类型
if ($stmt->execute()) {
echo "安全删除完成";
} else {
echo "删除失败:" . $stmt->error;
}
$stmt->close();
$conn->close();
?>
bind_param的常见类型:
-
i - 整数
-
d - 浮点数
-
s - 字符串
-
b - 二进制数据(比如图片文件)
PDO预处理
<?php
try {
$pdo = new PDO("mysql:host=localhost;dbname=company_db", "root", "123456");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("DELETE FROM employees WHERE id = :uid");
$stmt->bindParam(':uid', $employee_id);
$employee_id = 7;
$stmt->execute();
echo "PDO预处理删除成功";
} catch (PDOException $e) {
echo "出错了:" . $e->getMessage();
}
$pdo = null;
?>
个人见解: 我个人更偏好PDO的命名参数写法(:uid这种),一眼就能看出每个参数对应什么,不像?占位符还得数位置。项目里有七八个参数的时候,数问号能数到眼花。
实际案例:按条件批量删除
案例一:按精确ID删除
表名叫users,数据如下:
| id | username | |
|---|---|---|
| 1 | alan | alan@ebingou.cn |
| 2 | betty | betty@xxx.com |
| 3 | carl | carl@xxx.com |
| 4 | diana | diana@xxx.com |
删除id=3的记录:
DELETE FROM users WHERE id = 3
案例二:按条件批量删除
假设有个products表:
| id | product_name | category | price |
|---|---|---|---|
| 1 | 机械键盘 | 外设 | 299 |
| 2 | 游戏鼠标 | 外设 | 159 |
| 3 | 4K显示器 | 外设 | 1899 |
| 4 | C++入门 | 书籍 | 59 |
| 5 | Python实战 | 书籍 | 79 |
删除所有category='书籍'的记录:
DELETE FROM products WHERE category = '书籍'
删除后:
| id | product_name | category | price |
|---|---|---|---|
| 1 | 机械键盘 | 外设 | 299 |
| 2 | 游戏鼠标 | 外设 | 159 |
| 3 | 4K显示器 | 外设 | 1899 |
实际场景: 电商系统里下架某个分类的商品,或者论坛批量删除某个违规用户发的所有帖子,都是用WHERE条件批量处理。
本节课程知识要点
-
必须带WHERE条件 —— 不加条件等于TRUNCATE TABLE,数据直接没了
-
优先用预处理语句 —— 不是技术洁癖,是安全底线
-
MySQLi和PDO二选一 —— 新项目推荐PDO,数据库迁移成本低
-
执行删除前可以先SELECT —— 拿不准条件对不对,先用相同WHERE查一遍看看要删哪些
-
事务配合使用 —— 重要数据删除前开启事务,删错了还能回滚
// 事务示例
$pdo->beginTransaction();
$stmt = $pdo->prepare("DELETE FROM accounts WHERE balance < 0");
$stmt->execute();
// 确认无误再提交
$pdo->commit();
关于SQL注入再说两句
很多人觉得小网站没人攻击,没必要用预处理。但我经手过好几个被黑的站点,都不是什么大站,就是普通企业站。攻击者用扫描工具批量跑,找到没过滤的输入框直接注入。
用预处理不是性能问题,是态度问题。
再补充一个常见错误:用mysqli_real_escape_string去转义,觉得这样就安全了。这个函数只处理字符转义,不改变SQL语法结构。某些编码情况下依然有绕过方法。预处理是经过编译的SQL模板,参数和数据分两次发送,从根本上杜绝了注入。
常见错误码和排查思路
| 错误信息 | 大概率原因 |
|---|---|
| MySQL server has gone away | 超时或数据包太大 |
| Access denied | 用户名密码不对 |
| Table doesn't exist | 表名写错或库不对 |
| Cannot add or update a child row | 外键约束导致删不了 |
| Data too long for column | 跟DELETE无关,一般是INSERT/UPDATE |
遇到外键删不掉的,要么先删子表数据,要么临时关掉外键检查:
SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM orders WHERE id = 10;
SET FOREIGN_KEY_CHECKS = 1;
做完记得重新打开。