Skip to content

数据删除(DELETE)

在 PostgreSQL 中,删除数据是一个常见且重要的操作。与数据插入和更新一样,删除操作需要谨慎处理,特别是在生产环境中。本章将详细介绍如何安全有效地删除数据。

1. DELETE 语句基础概念

1.1 基本语法结构

DELETE 语句用于从表中删除符合条件的行。其基本语法如下:

sql
DELETE FROM table_name
WHERE condition;

重要特性

  • DELETE 操作是基于行的,您只能删除整行,而不能删除行中的特定列
  • 如果不使用 WHERE 子句,将删除表中的所有行
  • DELETE 是一个 DML(数据操作语言)操作,可以回滚

1.2 操作流程图

2. 基础删除操作

2.1 条件删除

最常见的删除操作是根据特定条件删除数据。

sql
-- 创建示例表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    price DECIMAL(10,2),
    category VARCHAR(50),
    stock_quantity INTEGER
);

-- 插入测试数据
INSERT INTO products (name, price, category, stock_quantity) VALUES
('笔记本电脑', 5999.99, '电子产品', 10),
('无线鼠标', 199.99, '电子产品', 50),
('办公椅', 899.99, '家具', 20),
('键盘', 299.99, '电子产品', 30),
('台灯', 159.99, '家具', 15);
sql
-- 删除价格等于特定值的产品
DELETE FROM products WHERE price = 199.99;

-- 删除价格大于1000的产品
DELETE FROM products WHERE price > 1000;

-- 删除特定类别的产品
DELETE FROM products WHERE category = '家具';

执行结果分析:

操作影响行数说明
DELETE FROM products WHERE price = 199.991删除无线鼠标
DELETE FROM products WHERE price > 10001删除笔记本电脑
DELETE FROM products WHERE category = '家具'2删除办公椅和台灯

2.2 复杂条件删除

sql
-- 使用多个条件
DELETE FROM products
WHERE price < 300 AND stock_quantity > 25;

-- 使用 IN 操作符
DELETE FROM products
WHERE category IN ('电子产品', '过时商品');

-- 使用模式匹配
DELETE FROM products
WHERE name LIKE '%鼠标%';

-- 使用日期条件(假设有创建时间字段)
DELETE FROM products
WHERE created_at < '2024-01-01';

2.3 全表删除

危险操作删除所有行是一个危险操作,请谨慎使用!

sql
-- 删除表中所有行
DELETE FROM products;

-- 等效操作,但更快(不能回滚)
TRUNCATE TABLE products;

DELETE vs TRUNCATE 比较:

特性DELETETRUNCATE
速度较慢很快
可回滚否(除非在事务中)
触发器会触发不会触发
WHERE 子句支持不支持
自增序列不重置重置

3. 高级删除操作

3.1 使用子查询删除

sql
-- 删除库存为0的产品
DELETE FROM products
WHERE id IN (
    SELECT id FROM products
    WHERE stock_quantity = 0
);

-- 删除价格低于平均价格的产品
DELETE FROM products
WHERE price < (
    SELECT AVG(price) FROM products
);

-- 删除同类别中价格最低的产品
DELETE FROM products p1
WHERE price = (
    SELECT MIN(price)
    FROM products p2
    WHERE p2.category = p1.category
);

3.2 关联表删除

当需要基于其他表的条件删除数据时:

sql
-- 创建相关表
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER,
    order_date DATE
);

-- 删除从未被订购的产品
DELETE FROM products
WHERE id NOT IN (
    SELECT DISTINCT product_id
    FROM orders
    WHERE product_id IS NOT NULL
);

-- 使用 EXISTS 子查询
DELETE FROM products p
WHERE NOT EXISTS (
    SELECT 1 FROM orders o
    WHERE o.product_id = p.id
);

3.3 批量删除

对于大量数据的删除,建议分批进行:

sql
-- 分批删除(每次删除1000行)
DO $$
DECLARE
    deleted_count INTEGER;
BEGIN
    LOOP
        DELETE FROM large_table
        WHERE some_condition
        LIMIT 1000;

        GET DIAGNOSTICS deleted_count = ROW_COUNT;

        -- 如果没有更多行可删除,退出循环
        IF deleted_count = 0 THEN
            EXIT;
        END IF;

        -- 提交中间结果并短暂休息
        COMMIT;
        PERFORM pg_sleep(0.1);
    END LOOP;
END $$;

4. 安全删除策略

4.1 删除前验证

最佳实践删除数据前,建议先使用 SELECT 语句验证将要删除的数据

sql
-- 第一步:查看将要删除的数据
SELECT * FROM products WHERE price < 100;

-- 第二步:确认无误后执行删除
DELETE FROM products WHERE price < 100;

-- 第三步:验证删除结果
SELECT COUNT(*) as remaining_count FROM products;

4.2 使用事务保护

sql
-- 开始事务
BEGIN;

-- 执行删除操作
DELETE FROM products WHERE category = '测试商品';

-- 检查结果
SELECT COUNT(*) FROM products WHERE category = '测试商品';

-- 如果满意,提交事务
COMMIT;
-- 如果不满意,回滚事务
-- ROLLBACK;

4.3 软删除策略

在某些业务场景中,推荐使用软删除而不是物理删除:

sql
-- 添加删除标记字段
ALTER TABLE products ADD COLUMN deleted_at TIMESTAMP;
ALTER TABLE products ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE;

-- 软删除(标记为已删除)
UPDATE products
SET is_deleted = TRUE, deleted_at = NOW()
WHERE id = 1;

-- 查询未删除的数据
SELECT * FROM products WHERE is_deleted = FALSE;

-- 恢复已删除的数据
UPDATE products
SET is_deleted = FALSE, deleted_at = NULL
WHERE id = 1;

5. 删除操作的返回值

5.1 RETURNING 子句

sql
-- 删除时返回被删除的行数据
DELETE FROM products
WHERE stock_quantity = 0
RETURNING id, name, price;

-- 删除并统计总价值
DELETE FROM products
WHERE category = '清仓商品'
RETURNING price,
          SUM(price * stock_quantity) OVER() as total_value;

5.2 获取影响行数

sql
-- 在存储过程或函数中
DO $$
DECLARE
    deleted_rows INTEGER;
BEGIN
    DELETE FROM products WHERE price < 50;
    GET DIAGNOSTICS deleted_rows = ROW_COUNT;
    RAISE NOTICE '删除了 % 行数据', deleted_rows;
END $$;

6. 性能优化

6.1 索引对删除性能的影响

sql
-- 为常用的删除条件创建索引
CREATE INDEX idx_products_category ON products(category);
CREATE INDEX idx_products_price ON products(price);
CREATE INDEX idx_products_stock ON products(stock_quantity);

-- 复合索引适用于多条件删除
CREATE INDEX idx_products_category_price ON products(category, price);

6.2 大批量删除优化

7. 常见错误和解决方案

7.1 外键约束错误

sql
-- 错误示例:尝试删除被引用的记录
DELETE FROM products WHERE id = 1;
-- ERROR: update or delete on table "products" violates foreign key constraint

-- 解决方案1:先删除子表记录
DELETE FROM orders WHERE product_id = 1;
DELETE FROM products WHERE id = 1;

-- 解决方案2:使用级联删除
ALTER TABLE orders
DROP CONSTRAINT orders_product_id_fkey,
ADD CONSTRAINT orders_product_id_fkey
    FOREIGN KEY (product_id)
    REFERENCES products(id)
    ON DELETE CASCADE;

7.2 权限不足错误

sql
-- 检查当前用户权限
SELECT has_table_privilege('products', 'DELETE');

-- 授予删除权限
GRANT DELETE ON products TO username;

8. 实际应用场景

8.1 数据清理任务

sql
-- 清理过期数据的存储过程
CREATE OR REPLACE FUNCTION cleanup_old_data()
RETURNS INTEGER AS $$
DECLARE
    deleted_count INTEGER := 0;
    total_deleted INTEGER := 0;
BEGIN
    -- 删除30天前的日志
    DELETE FROM application_logs
    WHERE created_at < NOW() - INTERVAL '30 days';
    GET DIAGNOSTICS deleted_count = ROW_COUNT;
    total_deleted := total_deleted + deleted_count;

    -- 删除已完成的临时订单
    DELETE FROM temp_orders
    WHERE status = 'completed'
    AND created_at < NOW() - INTERVAL '7 days';
    GET DIAGNOSTICS deleted_count = ROW_COUNT;
    total_deleted := total_deleted + deleted_count;

    RETURN total_deleted;
END;
$$ LANGUAGE plpgsql;

-- 执行清理
SELECT cleanup_old_data();

8.2 数据归档

sql
-- 归档旧数据的完整流程
CREATE TABLE products_archive (LIKE products INCLUDING ALL);

-- 移动旧数据到归档表
WITH moved_rows AS (
    DELETE FROM products
    WHERE created_at < '2023-01-01'
    RETURNING *
)
INSERT INTO products_archive SELECT * FROM moved_rows;

9. 监控和日志

9.1 删除操作监控

sql
-- 创建删除操作日志表
CREATE TABLE delete_operations_log (
    id SERIAL PRIMARY KEY,
    table_name VARCHAR(100),
    deleted_rows INTEGER,
    operation_time TIMESTAMP DEFAULT NOW(),
    user_name VARCHAR(100) DEFAULT current_user,
    conditions TEXT
);

-- 记录删除操作的触发器函数
CREATE OR REPLACE FUNCTION log_delete_operation()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO delete_operations_log (table_name, deleted_rows, conditions)
    VALUES (TG_TABLE_NAME, TG_OP, 'Trigger logged operation');
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

10. 总结

10.1 删除操作核心要点

删除操作最佳实践清单 ✅ 执行前必做:
  • 使用 SELECT 预览要删除的数据
  • 在测试环境中验证删除逻辑
  • 确保有完整的数据备份

执行中注意:

  • 使用事务保护重要操作
  • 对大量数据采用分批删除
  • 监控删除操作的性能影响

执行后检查:

  • 验证删除结果的正确性
  • 检查相关约束是否受影响
  • 更新相关统计信息

10.2 删除策略选择

场景推荐方案原因
少量数据删除直接 DELETE简单高效
大量数据删除分批删除避免长时间锁表
全表清空TRUNCATE性能最佳
需要恢复的删除软删除数据安全
历史数据处理归档+删除平衡性能和存储

重要提醒删除操作是不可逆的(除非在事务中回滚),因此在执行任何删除操作前,都应该:

  1. 确保有完整的数据备份
  2. 在测试环境中验证删除逻辑
  3. 使用事务保护重要的删除操作
  4. 考虑使用软删除策略代替物理删除

通过掌握这些删除操作的技巧和最佳实践,您可以在 PostgreSQL 中安全、高效地管理数据的删除需求。