使用UPDATE语句更新Room数据库变量时进度值丢失问题排查
问题分析与解决方案
问题核心
跨设备分片传输文件时,每次迭代从Room数据库读取当前progressDownload值,加上本次传输的transport.count后更新回数据库,但部分更新未被持久化,导致进度条无法达到100%。
原因诊断
这是典型的并发竞态条件:多个协程/线程同时读取数据库中的旧进度值,各自计算累加后执行更新,后执行的更新会覆盖先执行的,导致中间的传输字节数丢失。
代码问题展示
Dao层代码
@Query("UPDATE FileItem SET progressDownload = :value WHERE uuid = :uuid") suspend fun updateProgressDownload(uuid: String, value: Long)
Repository实现
override suspend fun updateProgressDownload(uuid: String, value: Long) { fileDao.updateProgressDownload(uuid, value) }
ViewModel逻辑
val currentValueDataBase = roomRepository.getItemByUuid(it)?.progressDownload val addANewValueToThePrevious = currentValueDataBase?.plus(transport.count) if (addANewValueToThePrevious != null) { roomRepository.updateProgressDownload(it, addANewValueToThePrevious) }
日志现象
日志显示多次进度更新操作中,部分累加后的进度值未被正确写入数据库,最终存储的进度值低于文件总大小对应的100%数值。
解决方案
方案1:数据库端原子更新(推荐)
直接在SQL语句中完成累加操作,利用数据库的原子性保证更新不丢失:
- 修改Dao层方法:
@Query("UPDATE FileItem SET progressDownload = progressDownload + :value WHERE uuid = :uuid") suspend fun incrementProgressDownload(uuid: String, value: Long)
- 更新Repository实现:
override suspend fun incrementProgressDownload(uuid: String, value: Long) { fileDao.incrementProgressDownload(uuid, value) }
- 简化ViewModel逻辑:
roomRepository.incrementProgressDownload(it, transport.count)
这种方式将累加操作放在数据库层面执行,单个UPDATE语句是原子的,完全避免竞态问题。
方案2:协程互斥锁控制
如果业务逻辑必须先读取值再处理,可以用Mutex保证同一时间只有一个协程执行读写操作:
- 在Repository中添加互斥锁:
private val progressUpdateMutex = Mutex() override suspend fun updateProgressDownload(uuid: String, addValue: Long) { progressUpdateMutex.withLock { val currentProgress = fileDao.getItemByUuid(uuid)?.progressDownload ?: 0L fileDao.updateProgressDownload(uuid, currentProgress + addValue) } }
- ViewModel中直接调用该方法即可,无需自行计算:
roomRepository.updateProgressDownload(it, transport.count)
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

