SQLx 遇到 SQLITE_FULL 后,下一次 begin_with 说事务还在
容量问题排除后普通 INSERT 恢复,BEGIN IMMEDIATE 仍因非零深度被拒绝。检查 SQLite 自动回滚与驱动事务记录的差别,隔离已失配连接。
一次事务内写入返回 SQLITE_FULL,排除容量限制后普通 INSERT 恢复了,begin_with("BEGIN IMMEDIATE") 却仍报 attempted to call begin_with at non-zero transaction depth。这时不能仅以“SQL 又能执行”判断驱动事务状态已经恢复。
SQLx 0.9.0 有这条具体边界。SQLite 遇到 SQLITE_FULL 等错误时可能自动回滚;事务对象随后丢弃,又安排一次 ROLLBACK。如果数据库已经没有活动事务,这次回滚失败,而 SQLx worker 只在回滚成功后减少内部深度,双方记录便可能失配。
先保留第一次错误,别手工补一个 BEGIN
确认最早错误确实是 SQLITE_FULL,并区分磁盘空间、数据库容量上限与其他资源约束。完整案例也可以由隔离内存库的 max_page_count 触发,不能据此断言宿主磁盘一定满了。
不要修改驱动私有深度、直接发送 BEGIN 绕过检查,或反复执行 ROLLBACK 直到不报错。数据库状态与驱动状态需要一致;跳过其中一层会让后续事务更难判断。
文件库可对这类操作隔离连接生命周期
对于持久化文件库,已经确认可能触发该状态的操作,可以在进入事务前标记专门取得的池连接:
use sqlx::{Connection, SqlitePool};
async fn write_batch(pool: &SqlitePool) -> Result<(), sqlx::Error> {
let mut conn = pool.acquire().await?;
conn.close_on_drop();
let mut tx = conn.begin().await?;
sqlx::query("INSERT INTO events(message) VALUES (?)")
.bind("example")
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(())
}
events 是示例结构,实际业务沿用原 SQL、绑定参数与事务要求。close_on_drop 使连接离开作用域后关闭,避免把可能失配的对象交回池。这也会关闭成功路径的连接,增加建连与初始化成本,只应在明确需要的操作范围采用;新连接仍要通过池的原有配置恢复 PRAGMA 等会话规则。
这段示例不适合直接搬到只有一份内容的内存数据库。关闭内存库连接可能删除整份数据库;共享内存库也有最后一个连接关闭的生命周期要求。应先确认数据持久化和恢复设计,不能为了清事务深度把数据一并丢掉。
换连接不等于写入成功
容量原因仍需解决,失败事务仍按失败处理。新连接能开始事务,只说明连接状态重新建立;原批次是否已经产生副作用、是否应重试,需按业务身份和提交记录核对。
在隔离文件副本或专门测试库中覆盖容量错误、事务释放、新连接恢复与正常写入,保留原数据库备份。不要在唯一生产库上主动压低容量或填满磁盘制造条件。保留原连接策略,撤回局部关闭策略前应确认所用正式版本已经处理该状态;恢复代码不会修复已失配的旧连接。


