在使用 JDBC 批量插入 YashanDB 时,有用户反馈触发器行为异常,明明是“语句级”触发器,却执行了 N 次。实际测试显示,在 executeBatch() 的场景下,语句级触发器会被重复触发,导致插入性能大幅下降。

本文将详细解析问题成因、验证方法与规避策略。

一、问题现象

某客户使用 JDBC 接口向表 A 插入数据:

表 A 上有一个语句级触发器(用于执行 alter sequence 等操作);

同时还存在一个行级触发器(用于赋值 sequence.nextval 或 current_timestamp)。

在插入 90 万条数据时,整个过程耗时高达 2.5 小时,性能远低于预期。

二、影响版本

该问题在以下版本均存在:

22.2.14.100 及以前版本

23.2.1.100 及以前版本

三、问题原因分析

经过内部排查,发现 YashanDB 在批量执行 SQL 时存在如下实现逻辑:

JDBC 的 executeBatch() 实际上会循环调用内部的 anlExecuteSingle(),导致语句级触发器被重复触发。

也就是说:

虽然你执行的是一条 batch 语句,数据库却当成多条语句分别执行,语句级触发器自然被重复触发了 N 次。

这显然不符合 SQL 标准行为,在 Oracle 中,语句级触发器只会在整批执行前或执行后触发一次。

四、验证方式

可使用如下方式进行验证:

建表并创建触发器:


drop table trig_test;
create table trig_test(t1 number, t2 number);
drop table flag;
create table flag(t number);
create or replace trigger trig_01
before insert or update on trig_test
begin
insert into flag values(1);
end;

Java 模拟批量插入代码:


public static void main(String[] args) {
try (Connection conn = DBUtil.getConn()) {
conn.setAutoCommit(false);
PreparedStatement ps = conn.prepareStatement("insert into trig_test(t1) values(?)");
for (int i = 0; i < 100; i++) {
ps.setInt(1. i);
ps.addBatch();
}
ps.executeBatch();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
}
}

验证触发次数:

select count(*) from flag;

你会发现 flag 表里有 100 条记录,也就是语句级触发器被触发了 100 次。
image.png
五、风险与影响
1745409791335.jpg
六、解决方案与规避建议

推荐方式:避免使用语句级触发器与executeBatch搭配

将语句级触发器逻辑迁移至业务代码控制;

或使用普通 insert 循环,牺牲一点性能换行为稳定。

临时规避:切换为 PL/SQL 块执行

使用单条 SQL + 触发一次触发器逻辑,例如:


begin
for i in 1..100 loop
insert into trig_test(t1) values(i);
end loop;
end;
/

七、官方解决方案

此问题已被官方定位为 驱动/执行器的设计缺陷,后续版本中将修复触发器触发逻辑,使其更符合标准行为。

八、经验总结
1745409774809.jpg


数据库砖家
1 声望0 粉丝