Slick事务查询未回滚:如何保证多表操作原子性?
解决Slick事务中部分操作失败未回滚的问题
哥们,你遇到的这个问题我之前也踩过坑,核心就是事务根本没真正生效,导致失败前的操作直接落到数据库里了。结合你用的Slick 3.2.3 + MySQL 5.7.22,咱们一步步排查解决:
先揪最可能的根源:MySQL表引擎不对
MySQL里的MyISAM引擎完全不支持事务!哪怕你在Slick里写了transactionally,只要表是MyISAM,失败操作之前的内容照样会保留,根本不会触发回滚。
先查下你的表用的啥引擎,跑这条SQL:
SHOW TABLE STATUS LIKE '你的表名';
看结果里的Engine列,如果是MyISAM,赶紧改成InnoDB:
ALTER TABLE 你的表名 ENGINE=InnoDB;
这一步是基础,不搞定这个后面都是白搭。
再调整你的Slick代码结构
你现在把asTry放在transactionally之后,这个顺序有点问题。虽然asTry能把异常转成Failure,但可能干扰了Slick的事务回滚逻辑——正确的做法是先把序列操作包成Try,再套事务控制:
def runTx(db: Database, queries: Iterable[DBIOAction[Int, NoStream, Effect]]) (implicit ec: ExecutionContext): Future[Try[Iterable[Int]]] = { val combined: DBIOAction[Iterable[Int], NoStream, Effect] = DBIO.sequence(queries) // 先转成Try,再加事务控制,确保异常能触发回滚 val tx: DBIOAction[Try[Iterable[Int]], NoStream, Effect with Effect.Transactional] = combined.asTry.transactionally db.run(tx) }
另外要注意:Slick的transactionally会接管整个事务的生命周期(开启、提交/回滚),别在你的插入操作里手动加事务相关的SQL,会和Slick的事务逻辑冲突。
最后验证事务原子性
写个小测试确认下:故意让第二个插入操作失败(比如插个重复的唯一键),然后看第一个操作的数据有没有被回滚。比如:
// 第一个正常插入操作 val insert1 = sqlu"INSERT INTO table1 (col) VALUES ('test_tx')" // 第二个故意触发唯一键冲突的操作 val insert2 = sqlu"INSERT INTO table2 (unique_col) VALUES ('已存在的值')" val testQueries = List(insert1, insert2) runTx(db, testQueries).onComplete { case Success(Failure(_)) => // 检查table1里有没有'test_tx',没有就说明回滚成功了 db.run(sql"SELECT COUNT(*) FROM table1 WHERE col = 'test_tx'".as[Int].head).map { count => assert(count == 0, "糟了,事务没回滚,数据留着了") } case _ => println("测试情况不符合预期") }
额外提醒
- 确认你的Slick数据库配置没开自动提交(默认是关闭的,但如果手动修改过配置就需要检查)。
- 如果用Slick Table API写插入逻辑,也要确保对应的底层表是InnoDB引擎。
内容的提问来源于stack exchange,提问作者kostja
相关产品推荐
相关产品推荐

