Oracle SQL批量更新重复STUDENT_ID字段及性能优化问询
STUDENT表STUDENT_ID字段批量更新方案及性能优化
需求很明确:给STUDENT表的STUDENT_ID字段加前缀,唯一的记录加SS,重复的按顺序加SS1、SS2... 先看示例:
更新前
| 编号 | STUDENT_ID |
|---|---|
| 1 | 9234 |
| 2 | 9234 |
| 3 | 9234 |
| 4 | 3456 |
| 5 | 3456 |
| 6 | 789 |
| 7 | 956 |
更新后
| 编号 | STUDENT_ID |
|---|---|
| 1 | SS9234 |
| 2 | SS19234 |
| 3 | SS29234 |
| 4 | SS3456 |
| 5 | SS13456 |
| 6 | SS789 |
| 7 | SS956 |
你之前写的全表更新语句update student set student_id = 'SS'||student_id ;在4000万数据量的表上绝对不能用——全表更新会锁表、占满日志,直接影响业务。下面给你针对100万重复记录的精准更新方案,再附性能优化建议:
一、精准更新方案
1. 先处理唯一记录(如果还没执行全表更新)
别全表扫描,只给没有重复的STUDENT_ID加前缀:
-- 仅给无重复的STUDENT_ID添加SS前缀 UPDATE student SET student_id = 'SS' || student_id WHERE student_id IN ( SELECT student_id FROM student GROUP BY student_id HAVING COUNT(*) = 1 ); COMMIT;
2. 处理重复记录
用窗口函数给每个重复组的记录编序号,再根据序号拼接前缀:
-- 用CTE标记重复记录的序号,再更新(支持PostgreSQL、Oracle、MySQL 8.0+) WITH student_duplicates AS ( SELECT 编号, student_id, -- 按STUDENT_ID分组,按编号排序生成序号 ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY 编号) AS rn FROM student -- 只筛选有重复的STUDENT_ID WHERE student_id IN ( SELECT student_id FROM student GROUP BY student_id HAVING COUNT(*) > 1 ) ) UPDATE student s SET student_id = CASE WHEN sd.rn = 1 THEN 'SS' || sd.student_id -- 组内第一条加SS ELSE 'SS' || (sd.rn - 1) || sd.student_id -- 后面的加SS1、SS2... END FROM student_duplicates sd WHERE s.编号 = sd.编号; COMMIT;
如果你的数据库不支持CTE关联更新(比如老版本MySQL),用子查询关联:
UPDATE student s JOIN ( SELECT 编号, student_id, ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY 编号) AS rn FROM student WHERE student_id IN ( SELECT student_id FROM student GROUP BY student_id HAVING COUNT(*) > 1 ) ) sd ON s.编号 = sd.编号 SET s.student_id = CASE WHEN sd.rn = 1 THEN 'SS' || sd.student_id ELSE 'SS' || (sd.rn - 1) || sd.student_id END; COMMIT;
二、性能优化建议
针对4000万数据量和100万重复记录,这些优化能让你少踩坑:
- 给STUDENT_ID加索引:分组查询重复记录时,索引能直接定位重复值,避免全表扫描。更新前记得更新表统计信息(Oracle用
DBMS_STATS.GATHER_TABLE_STATS('your_schema','student'),MySQL用ANALYZE TABLE student;)。 - 分批更新:别一次性更100万条,拆成每次1万-5万条的小批次,避免事务日志爆仓、锁表时间太长。比如Oracle的批量更新示例:
DECLARE v_batch_size NUMBER := 10000; -- 每次更1万条 v_max_id NUMBER; v_current_id NUMBER := 0; BEGIN SELECT MAX(编号) INTO v_max_id FROM student; WHILE v_current_id < v_max_id LOOP UPDATE student s SET student_id = CASE WHEN sd.rn = 1 THEN 'SS' || sd.student_id ELSE 'SS' || (sd.rn - 1) || sd.student_id END FROM ( SELECT 编号, student_id, ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY 编号) AS rn FROM student WHERE 编号 > v_current_id AND 编号 <= v_current_id + v_batch_size AND student_id IN (SELECT student_id FROM student GROUP BY student_id HAVING COUNT(*) > 1) ) sd WHERE s.编号 = sd.编号; COMMIT; -- 每批提交一次 v_current_id := v_current_id + v_batch_size; END LOOP; END; /
- 离线更新(如果允许):如果业务能接受短时间只读,直接导出数据到临时表处理完再替换原表,比直接更新快N倍:
-- 创建临时表并插入处理后的数据 CREATE TABLE student_new AS SELECT 编号, CASE WHEN rn = 1 THEN 'SS' || student_id ELSE 'SS' || (rn - 1) || student_id END AS student_id FROM ( SELECT 编号, student_id, ROW_NUMBER() OVER (PARTITION BY student_id ORDER BY 编号) AS rn FROM student ); -- 验证数据没问题后,替换原表 DROP TABLE student; ALTER TABLE student_new RENAME TO student; -- 重建原表的索引、约束、触发器
- 临时禁用触发器/非必要约束:更新前如果有针对STUDENT_ID的触发器,或者外键、检查约束这类非必要的(更新期间可以临时禁用),先关掉,更新完再开,减少额外开销。
- 低峰时段执行:选凌晨或者业务最闲的时候操作,别影响正常业务。
内容的提问来源于stack exchange,提问作者Bruce
相关产品推荐
相关产品推荐

