第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 事务隔离的核心结构图

Room数据库事务隔离核心结构 应用层 DAO @Transaction + SQL Room 事务管理器 begin/commit/rollback SQLite 引擎 锁 + 日志 + WAL 隔离级别树(由低到高) READ UNCOMMITTED READ COMMITTED REPEATABLE READ SERIALIZABLE 说明 SQLite默认使用SERIALIZABLE;Room通过@Transaction保证原子性和隔离性 A 原子性 C 一致性 I 隔离性 D 持久性

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();