sql存储过程本身不加速查重或删重,真正影响速度的是查询写法、索引、数据量和数据库版本;盲目封装反而可能掩盖性能瓶颈。

直接说结论:SQL 存储过程本身不加速查找或删除重复记录,它只是把已有逻辑封装起来;真正影响速度的是底层查询写法、索引、数据量和数据库版本。盲目封装成存储过程反而可能掩盖性能瓶颈。
为什么用存储过程查重容易踩坑
很多人以为“写成存储过程=自动优化”,其实不然。存储过程不会改变 SQL 本身的执行计划,反而可能因参数嗅探、缓存计划失效、缺乏实时统计信息导致比手写语句更慢。
-
EXEC sp_delete_duplicates执行时若没加WITH RECOMPILE,可能复用旧执行计划,对新数据效果极差 - 传入字段名作为字符串参数(如
@key_columns NVARCHAR(200))会迫使你用EXEC(@sql)拼接动态 SQL,失去编译期检查,也难加索引提示 - 没有预判
GROUP BY字段是否已建索引,直接在千万行表上跑SELECT ... GROUP BY email, name,I/O 和排序开销爆炸
真正有效的存储过程写法(以 PostgreSQL / SQL Server 为例)
只封装**确定性高、参数固定、可预建索引**的场景。例如:按 email 去重,且该字段已有唯一索引或 B-tree 索引。
- 必须显式限定去重字段,不支持泛化列名拼接 —— 避免动态 SQL
- 内部用
ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at)标记,而非NOT IN (SELECT MIN(id) ...)(后者在大表上易锁表、内存溢出) - 加上
RAISE NOTICE或PRINT输出实际删除行数,方便核对 - 示例核心逻辑(PostgreSQL):
CREATE OR REPLACE PROCEDURE clean_users_by_email()
LANGUAGE plpgsql AS $$
DECLARE
deleted_count INTEGER;
BEGIN
WITH duplicates AS (
SELECT id,
ROW_NUMBER() OVER (PARTITION BY email ORDER BY created_at) AS rn
FROM users
WHERE email IS NOT NULL
)
DELETE FROM users
WHERE id IN (SELECT id FROM duplicates WHERE rn > 1);
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RAISE NOTICE 'Deleted % duplicate rows', deleted_count;
END;
$$;
MySQL 8.0+ 存储过程中必须绕过的限制
MySQL 在存储过程里执行 DELETE ... WHERE id IN (SELECT ...) 会报错 Error 1093: You can't specify target table 'users' for update in FROM clause,这不是语法问题,是引擎限制。
- 不能直接在
DELETE中嵌套引用同一张表的子查询 - 正确解法:用 CTE + 临时表中转,或改用自连接(但自连接在存储过程中写法笨重)
- 推荐做法:在存储过程里先建临时表存待删
id,再DELETE FROM users WHERE id IN (SELECT id FROM temp_to_delete) - 务必在过程末尾
DROP TEMPORARY TABLE temp_to_delete,否则下次调用失败
比存储过程更值得优先做的事
删重慢,90% 不是因为没写存储过程,而是因为没做这三件事:
- 确认去重字段(如
email)上有索引 ——CREATE INDEX idx_users_email ON users(email); - 避免在
WHERE条件里对去重字段做函数操作,比如LOWER(email),会失索引 - 大数据量时分批删:
DELETE FROM users WHERE id IN (...) LIMIT 1000;循环执行,防长事务锁表
存储过程只是壳,底下的查询逻辑、索引设计、执行节奏,才是决定快慢的关键。别为了“看起来规范”而提前封装,先让单条语句跑通、跑稳、跑快再说。











