1. INSERT INTO SELECT:跨表搬运工的高效玩法
第一次接触Oracle批量插入时,我像发现新大陆一样兴奋——原来不需要写几百条INSERT语句也能搞定海量数据。INSERT INTO SELECT就是我的启蒙老师,它的工作原理就像搬家公司的集装箱卡车,直接把整个仓库的货物整箱搬运到新地址。
上周刚用这个方案处理了电商平台的订单迁移:把三个月前的历史订单从业务表转移到归档表。核心代码简单到不可思议:
INSERT INTO order_archive SELECT * FROM order_active WHERE create_time < ADD_MONTHS(SYSDATE, -3);执行过程只用了12秒就转移了47万条记录,比Java程序逐条插入快了近200倍。这里有个实战技巧:如果源表和目标表字段顺序不一致,务必显式指定字段列表:
-- 错误示范(字段顺序不匹配会导致数据错位) INSERT INTO employee_backup SELECT * FROM employee; -- 正确姿势 INSERT INTO employee_backup (id, name, dept) SELECT emp_id, emp_name, dept_code FROM employee;临时表方案在数据清洗时特别管用。最近做用户画像项目时,我先用CTAS(Create Table As Select)创建包含过滤条件的临时表:
CREATE TABLE temp_high_value_users AS SELECT user_id, last_login_ip, purchase_amount FROM users WHERE purchase_amount > 10000;然后再把临时表数据插入目标系统,这样既能避免锁表现象,又方便中间结果验证。注意临时表用完要及时清理,我有次忘记DROP TABLE导致临时表把磁盘撑爆的惨痛教训。
2. INSERT ALL:Oracle的批量插入黑科技
当需要在同个事务插入多表数据时,INSERT ALL就像瑞士军刀般全能。去年开发财务系统时,每笔交易要同时写入交易主表和明细表,用这个语法完美解决:
INSERT ALL INTO transaction_main (txn_id, txn_date) VALUES (seq_txn.nextval, SYSDATE) INTO transaction_detail (txn_id, item_code) VALUES (seq_txn.currval, 'ITEM001') SELECT 1 FROM DUAL;这里有个隐藏技巧:用序列的nextval和currval组合获取相同的主键值。实测插入1万条记录仅耗时3.8秒,比分开执行两条INSERT快60%。
更骚的操作是配合CASE WHEN实现条件插入。在用户权限管理系统里,我这样处理不同角色权限的批量分配:
INSERT ALL WHEN role_type = 'ADMIN' THEN INTO admin_priv (user_id, priv_code) VALUES (user_id, 'ALL') WHEN role_type = 'AUDIT' THEN INTO audit_priv (user_id, priv_code) VALUES (user_id, 'READ_ONLY') SELECT user_id, role_type FROM user_roles;注意DUAL表在这个语法中的特殊作用——它就像个触发器,SELECT子句返回多少行就会执行多少次插入。有次我手滑写了SELECT * FROM big_table,结果插入了百万条重复数据...
3. FORALL:PL/SQL开发者的性能核弹
处理千万级数据迁移时,FORALL让我见识了什么叫降维打击。它的性能提升主要来自两方面:减少上下文切换(相比普通循环减少90%以上)和绑定变量优化。看这个物流系统的实际案例:
DECLARE TYPE tracking_tab IS TABLE OF tracking_log%ROWTYPE; v_data tracking_tab; BEGIN -- 批量获取待处理数据 SELECT * BULK COLLECT INTO v_data FROM raw_tracking_data WHERE process_flag = 'N'; -- 批量插入(实测10万条/秒) FORALL i IN 1..v_data.COUNT INSERT INTO tracking_log VALUES v_data(i); -- 更新处理标志 FORALL i IN 1..v_data.COUNT UPDATE raw_tracking_data SET process_flag = 'Y' WHERE id = v_data(i).id; END;关键点在于BULK COLLECT的合理使用:太小的LIMIT影响性能,太大的容易引发ORA-04030内存错误。经过多次测试,我发现5万-10万的批处理大小在大多数服务器上表现最佳。
保存点(SAVEPOINT)在批量操作中特别重要。有次ETL任务执行到一半报错,因为没设保存点导致整个事务回滚。现在我的代码里一定会加异常处理:
BEGIN SAVEPOINT start_migration; -- 批量操作代码... EXCEPTION WHEN OTHERS THEN ROLLBACK TO start_migration; DBMS_OUTPUT.PUT_LINE('Error: '||SQLERRM); END;4. 方案选型:从理论到实战的决策树
面对具体业务场景时,我总结出这样的决策路径:
数据来源:
- 来自其他表/子查询 → INSERT INTO SELECT
- 需要程序生成 → FORALL
- 固定值多行插入 → INSERT ALL
性能对比测试结果(插入10万条记录):
| 方案 | 耗时(秒) | 内存占用(MB) | 适用场景 |
|---|---|---|---|
| INSERT INTO SELECT | 2.3 | 15 | 表间数据迁移 |
| INSERT ALL | 5.1 | 22 | 多表插入/条件插入 |
| FORALL | 1.8 | 90 | 程序生成数据批量插入 |
| 普通LOOP循环 | 48.7 | 8 | 不推荐 |
在数据仓库项目中,我通常组合使用这些方案。比如先用INSERT INTO SELECT把源数据加载到临时表,经清洗转换后,再用FORALL灌入目标表。特别注意批量提交的优化技巧:
-- 不好的做法:全程单事务 BEGIN FORALL i IN 1..100000 INSERT INTO sales_data VALUES(...); COMMIT; END; -- 优化方案:分批提交 BEGIN FOR i IN 1..10 LOOP FORALL j IN 1..10000 INSERT INTO sales_data VALUES(...); COMMIT; -- 每1万条提交一次 END LOOP; END;字段映射是另一个容易踩坑的点。有次迁移时没注意NUMBER(10)和NUMBER(10,2)的区别,导致金额数据被截断。现在我的检查清单必含:
- 字段类型兼容性验证
- 非空约束检查
- 索引/触发器对性能的影响评估
- 批量大小与UNDO表空间的匹配