PHP MySQL Delete

Deleting rows, soft deletes, and safe bulk deletion.

Beginner
Key PointsHard deletes are irreversible — prefer soft deletes with `deleted_at`.

Delete one row

PHP
<?php
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([3]);

echo "删除行数: " . $stmt->rowCount() . "\n";

$left = (int)$pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
echo "剩余: $left\n";
Output
删除行数: 1
剩余: 2

Soft delete

Business data is rarely hard-deleted. Add deleted_at, write a timestamp on delete, filter on read. Recoverable and auditable.

SQL
ALTER TABLE users ADD COLUMN deleted_at DATETIME NULL DEFAULT NULL;
ALTER TABLE users ADD KEY idx_deleted (deleted_at);
PHP
<?php
// 删除
$stmt = $pdo->prepare("UPDATE users SET deleted_at = NOW() WHERE id = ?");
$stmt->execute([2]);
echo "已标记删除: " . $stmt->rowCount() . " 行\n";

// 查询时过滤
$rows = $pdo->query("SELECT name FROM users WHERE deleted_at IS NULL ORDER BY id")
            ->fetchAll(PDO::FETCH_COLUMN);
echo "有效记录: " . implode(", ", $rows) . "\n";
Output
已标记删除: 1 行
有效记录: Tom
The cost: **every query needs deleted_at IS NULL** and one omission leaks data. Use a view or an ORM global scope rather than discipline.

Bulk delete

PHP
<?php
$ids = [1, 2, 5];

$in = implode(",", array_fill(0, count($ids), "?"));
$stmt = $pdo->prepare("DELETE FROM users WHERE id IN ($in)");
$stmt->execute($ids);

echo "删除行数: " . $stmt->rowCount() . "\n";
Output
删除行数: 3

Cast every ID to int, then build placeholders. Return early on an empty array — IN () is a syntax error.

Deleting from big tables

Deleting millions of rows locks the table, bloats the undo log and lags replication. Delete in batches with a pause.

PHP
<?php
$batch = 1000;
$total = 0;

while (true) {
    $stmt = $pdo->prepare("DELETE FROM users WHERE deleted_at IS NOT NULL LIMIT ?");
    $stmt->bindValue(1, $batch, PDO::PARAM_INT);
    $stmt->execute();

    $n = $stmt->rowCount();
    $total += $n;
    if ($n < $batch) {
        break;
    }
    usleep(100000);          // 100ms,给主从同步留时间
}

echo "共删除 $total 行,每批 $batch 行\n";
Output
共删除 2500 行,每批 1000 行
LIMIT in a DELETE needs bindValue(..., PDO::PARAM_INT); otherwise PDO sends a string and MySQL rejects it.

TRUNCATE vs DELETE

AspectDELETETRUNCATE
WHERE支持不支持
Transaction是否(DDL)
Speed逐行,慢重建表,快
Auto-increment保留重置为 1
Triggers会触发不触发
Use for按条件删清空整表
TRUNCATE is not rollback-able, skips triggers and needs DROP privilege. Check foreign keys first or it errors out.

Friends & Partners