最常用且可控的方式是FOR循环+INSERT,但10万条以上应避免单条提交;需批量绑定(BULK COLLECT/FORALL)、合理提交时机、用序列生成ID、慎用TO_DATE、关闭日志或改用SQL*Loader。
直接上结论:用 for 循环 + insert 是最常用、最可控的方式,但 10 万条以上数据不建议在 pl/sql 块里单条 insert 提交,否则慢到怀疑人生。
为什么不能无脑写 FOR 循环插入?
Oracle 的单条 INSERT 在 PL/SQL 块中默认是隐式游标操作,每执行一次都走完整解析 → 执行 → 提交(如果加了 COMMIT)流程。10 万次就是 10 万次上下文切换 + 日志刷盘,实测可能耗时几分钟甚至十几分钟。
- 常见错误现象:
ORA-01555: snapshot too old(循环太久,undo 被覆盖)或会话卡死、PL/SQL Developer 报 “timeout” - 真正该提交的时机:只在循环结束后
COMMIT一次;若必须分批,用MOD(i, 1000) = 0控制每 1000 条提交一次 - 性能影响:单条插入 10 万行 ≈ 5–15 分钟;批量绑定(
BULK COLLECT/FORALL)可压到 2–5 秒,但需要额外变量结构
如何安全生成带自增 ID 的测试数据?
别硬拼字符串或用 ROWNUM,优先用序列(SEQUENCE)——它线程安全、不阻塞、支持缓存(CACHE 1000 可大幅提升性能)。
- 建序列示例:
CREATE SEQUENCE test_seq START WITH 1 INCREMENT BY 1 CACHE 1000; - 循环中取值:
v_id := test_seq.NEXTVAL;,不是test_seq.CURRVAL(首次调用会报错) - 替代方案(仅限 UUID 场景):
SYS_GUID()无需建对象,但返回的是 RAW(16),常需转成 VARCHAR2:RAWTOHEX(SYS_GUID()) - 注意:不要在
VALUES里直接写test_seq.NEXTVAL多次——同一语句中多次引用会得到相同值
时间字段怎么填才不出错?
TO_DATE 和 SYSDATE 行为差异大,选错会导致全表时间戳一模一样,或插入失败。
- 固定时间点(如“2024-01-01 00:00:00”):
TO_DATE('2024-01-01 00:00:00', 'yyyy-mm-dd hh24:mi:ss'),格式串必须严格匹配字符串 - 动态偏移时间(推荐):
SYSDATE + i/86400(每行加 1 秒),或TRUNC(SYSDATE) + i/100(当天内均匀分布) - 避免踩坑:
TO_DATE('2024-01-01', 'yyyy-mm-dd')会把时分秒设为 00:00:00,但字段是TIMESTAMP也没问题;而TO_DATE('2024-01-01')不带格式串会依赖NLS_DATE_FORMAT,极不稳定
10 万条以上数据的底线优化建议
真要插 10 万+,就别只靠裸 FOR 循环。以下三点至少做到其一:
- 关掉日志(仅测试库):
ALTER TABLE your_table NOLOGGING;,插入完再ALTER TABLE your_table LOGGING; - 用
BULK COLLECT+FORALL替代逐行插入(需定义TYPE t_tab IS TABLE OF your_table%ROWTYPE) - 改用 SQL*Loader 或外部表(
CREATE TABLE ... ORGANIZATION EXTERNAL),从 CSV 文件导入,速度提升 10 倍起 - 最后提醒:
DBMS_OUTPUT.PUT_LINE在大量循环里频繁调用会拖慢 20%+,调试完务必删掉或注释
真正难的不是写出来,而是想清楚:这批数据要不要索引?要不要触发器?要不要约束校验?这些开关开着,插入速度可能直接打五折。动手前先 SELECT COUNT(*) FROM user_constraints WHERE table_name = 'YOUR_TABLE' 看一眼。











