SQLite 中多行 REPLACE 为何触发过多级联插入?
结论
是的,外层的 REPLACE INTO arrivals 很可能是这个现象的原因。SQLite 的冲突处理规则规定,带有冲突策略的语句触发触发器时,触发器内的语句也会采用外层语句的冲突处理策略。因此,外层的 REPLACE 可能覆盖触发器中 INSERT OR IGNORE INTO places 的 IGNORE。这样一来,places 发生唯一键冲突时,旧行可能会被替换。
重复的 airport_code 因而不一定会被忽略:旧的 places 行被替换后,新行插入,AFTER INSERT 触发器也会运行。因此,places 的触发器可能执行超过 40 次。
为什么 _vec_queue 可能增加多行
places 上的 UNIQUE 索引约束的是 airport_code。如果冲突处理策略变成 REPLACE,每次替换都会先删除冲突行,再插入新行。由于 id 是 INTEGER PRIMARY KEY,新行的 id 可能发生变化。_vec_queue 的唯一索引约束的是 (item_type, item_id, vecIndex),因此不同的 item_id 可以对应不同的队列行。
给出的 _vec_queue 定义中确实包含唯一索引:
CREATE UNIQUE INDEX unique_item_type_and_id_and_vec_index
ON _vec_queue (item_type, item_id, vecIndex)
不过,这个索引不会阻止同一机场代码对应的不同 item_id 各自产生队列行。仅凭给出的片段,也无法断定队列行数一定是 110;实际数量还取决于替换时生成的 places.id,以及队列中已有的键值。
处理方式
如果希望 places 遇到重复机场代码时跳过插入,先避免通过外层 REPLACE 执行这次写入。若业务允许,可以改用普通 INSERT,让触发器中的 INSERT OR IGNORE 按预期处理 places 的唯一键冲突:
INSERT INTO arrivals (<columns-here>)
VALUES
(<row-1-values-here>),
(<row-2-values-here>);
如果 arrivals 本身也需要处理 flight_history_id 冲突,应先明确需要的行为,再使用相应的 INSERT ... ON CONFLICT ... 策略,或拆分写入逻辑。不要默认使用外层 REPLACE,同时期待触发器内的 OR IGNORE 独立生效。具体的冲突目标和更新字段需按业务需求确定。
检查事项
- 确认实际执行的 SQL,而不只是应用层构造的语句。外层语句是
REPLACE INTO,这会影响诊断。 - 检查重复写入相同机场代码时,
places的id是否改变,并核对_vec_queue中对应的(item_type, item_id, vecIndex)。 - 给出的
places建表片段在airport text not null后有一个尾随逗号。如果这不是粘贴时的笔误,SQL 需要先修正才能执行。
备注:内容仅供参考。
