第1章 Room数据库隔离基础:事务定义、ACID特性、隔离级别概述
1.1 事务定义与核心属性
在Room(基于SQLite)中,事务(Transaction)是一组数据库操作的最小逻辑执行单元。事务必须满足ACID特性:
- 原子性(Atomicity):事务内的所有操作要么全部成功提交,要么全部回滚,不存在部分完成的状态。
- 一致性(Consistency):事务执行前后,数据库必须保持所有预定义的约束、规则和业务完整性。
- 隔离性(Isolation):并发执行的事务之间相互隔离,一个事务的中间状态对其他事务不可见。
- 持久性(Durability):已提交的事务结果永久保存在数据库中,即使系统崩溃也不会丢失。
1.2 Room中的事务实现:@Transaction注解
Room提供@Transaction注解标记DAO方法,确保方法内所有数据库操作在同一数据库连接中顺序执行,并自动处理beginTransaction、setTransactionSuccessful和endTransaction。
@Dao
public interface UserDao {
@Transaction
@Query("SELECT * FROM users WHERE id = :userId")
User getUserWithPosts(int userId);
}
// 多操作复合事务
@Dao
public interface TransactionDao {
@Transaction
default void transferPoints(int fromId, int toId, int points) {
deduct(fromId, points);
add(toId, points);
}
@Query("UPDATE accounts SET points = points - :p WHERE id = :id")
void deduct(int id, int p);
@Query("UPDATE accounts SET points = points + :p WHERE id = :id")
void add(int id, int p);
}
1.3 SQLite隔离级别与Room默认行为
SQLite默认使用SERIALIZABLE隔离级别(通过数据库级别的读写锁实现),在普通@Transaction中表现为可串行化。可通过PRAGMA read_uncommitted调整为READ COMMITTED(仅限读取时)。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | SQLite支持 | Room启用方式 |
|---|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 否(SQLite始终提供读锁) | — |
| READ COMMITTED | 避免 | 可能 | 可能 | 是(通过PRAGMA) | 在打开数据库时执行rawQuery("PRAGMA read_uncommitted = 1")(不推荐) |
| REPEATABLE READ | 避免 | 避免 | 可能 | 否(等价于SERIALIZABLE) | — |
| SERIALIZABLE | 避免 | 避免 | 避免 | 是(默认) | 默认使用,无需额外配置 |
1.4 事务隔离的核心结构图
1.5 调试与常见问题
- 调试:启用SQLite日志(
adb shell setprop log.tag.SQLiteLog V)可观察事务开始/提交/回滚。 - 常见错误1:事务未提交:在
@Transaction方法中抛出未捕获异常会导致事务自动回滚,可能丢失部分操作。 - 常见错误2:嵌套事务:Room默认不支持嵌套事务,内部
@Transaction会被合并到外层事务中。如果需要独立提交,应拆分DAO方法。 - 常见错误3:死锁:长时间持有读锁再尝试写锁可能导致SQLite锁超时(
SQLiteBusyException)。建议使用@Transaction保持短小。
// 错误示例:嵌套事务导致意外回滚
@Dao
public interface BadDao {
@Transaction
default void outer() {
inner(); // 内部事务也属于同一事务
if (someCondition) throw new RuntimeException(); // 整个outer回滚
}
@Transaction
default void inner() {
// 此方法内的操作不会被单独提交
}
}
// 正确做法:将inner逻辑直接写在outer中,或用回调细粒度控制
1.6 性能与稳定性优化建议
- 批量操作合并:将多次INSERT/UPDATE放入同一个
@Transaction,减少I/O和锁竞争。 - 避免长事务:单事务内操作超过100条记录时,考虑分批提交,防止ANR或SQLite锁超时。
- 使用WAL模式:在Room的
databaseBuilder中启用WAL(.enableWalMode(true)),提高读写并发。 - 隔离级别权衡:如果应用只容忍脏数据而不允许阻塞,可考虑通过
rawQuery设置PRAGMA read_uncommitted = 1,但注意这会丧失可重复读,需在业务上补偿。
// 启用WAL模式(Room 2.1+)
Room.databaseBuilder(context, AppDatabase.class, "app.db")
.enableWalMode(true)
.build();