You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:55:09