0赞
赏
赞赏
更多好文
上个月,我们电商App的订单系统崩了。
用户下单扣了钱,但库存没减——系统显示“已发货”,实际仓库没动。
查了3小时,发现是数据库事务没处理好。
当时就想:Room用得这么烂,不如重写SQLite算了。
后来我们彻底重构了Room用法,现在订单数据100%一致,查询快得像开了挂。
下面全是血泪经验,没理论,全是能抄的代码。
一、为什么Room是救命稻草?先看崩溃现场
传统SQLite的坑(我们踩过):
// 伪代码:下单操作(没事务)
public void placeOrder(Order order) {
db.insertOrder(order); // 插入订单
db.updateStock(order.productId, -1); // 减库存
// 如果第二步失败?订单有,库存没减!
}
后果:
- 50%的订单数据不一致(用户投诉“钱扣了,东西没到”)
- 查询慢得像老牛拉车(1000条数据查500ms+)
Room的救命点:
- 事务自动保证原子性(要么全成功,要么全失败)
- 查询优化(用
@Query+索引,快10倍) - 迁移安全(版本升级不丢数据)
二、实战优化:3步搞定高效查询、迁移、事务
1. 高效查询:别让SQL慢成PPT
痛点:
SELECT * FROM orders WHERE user_id = ? 慢得要命,用户等得想卸载。
优化方案:
- 加索引(Room自动建,不用手写SQL)
@Entity(indices = [Index(value = ["user_id"])]) data class Order( @PrimaryKey val id: Int, val user_id: Int, val product_id: Int ) - 用
@Query精准查询(别用SELECT *)@Dao interface OrderDao { @Query("SELECT * FROM orders WHERE user_id = :userId") fun getOrdersByUser(userId: Int): List<Order> }✅ 实测:查询从500ms→50ms(1000条数据),用户说“点一下就出来了”。
避坑:
- 别在
@Query里用LIKE(比如WHERE name LIKE '%keyword%')——会全表扫描!
用FULLTEXT索引或改用WHERE name = :keyword。
2. 数据库迁移:版本升级别让数据“蒸发”
崩溃现场:
v1.0的App用orders表,v2.0要加status字段。
直接改@Entity,用户升级后:所有数据丢失!
正确做法:
- 写Migration类(Room自动处理)
val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL("ALTER TABLE orders ADD COLUMN status INTEGER NOT NULL DEFAULT 0") } } - 在RoomDatabase里注册
@Database(entities = [Order::class], version = 2) abstract class AppDatabase : RoomDatabase() { abstract fun orderDao(): OrderDao companion object { fun getInstance(context: Context): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app.db" ) .addMigrations(MIGRATION_1_2) // 关键!注册迁移 .build() } } }💡 经验:每次改表结构,必须写Migration!
之前团队忘了写,用户升级后数据全空,客服热线打爆。
3. 事务管理:下单操作必须原子化
崩溃现场:
用户下单时,先插订单,再减库存。
如果减库存失败,订单却有了——钱扣了,东西没到。
Room解决方案:
- 用
@Transaction包裹操作(保证要么全成功,要么全失败)class OrderRepository(private val orderDao: OrderDao) { @Transaction fun placeOrder(order: Order) { orderDao.insert(order) // 插入订单 orderDao.updateStock(order.productId, -1) // 减库存 // 如果第二步失败,整个操作回滚! } }✅ 实测:订单数据100%一致,用户投诉从50%→0%。
避坑:
- 别在事务里调用外部API(比如网络请求)——事务会锁表,导致超时!
事务只做本地数据库操作。
三、我们踩过的3个雷,别再踩
-
雷1:忘了加索引,查询慢成龟速
- 问题:
getOrdersByUser没索引,1000条数据查500ms。 - 解法:在
@Entity加indices = [Index(value = ["user_id"])]。 - 教训:所有查询字段,必须加索引。
- 问题:
-
雷2:迁移时没处理旧数据
- 问题:v1.0的
orders表没status,v2.0直接加字段,但没初始化默认值。
用户看到订单状态是null,崩溃。 - 解法:在
Migration里初始化默认值:database.execSQL("ALTER TABLE orders ADD COLUMN status INTEGER NOT NULL DEFAULT 0") - 教训:迁移时,新字段必须给默认值。
- 问题:v1.0的
-
雷3:事务里塞了网络请求
- 问题:在
@Transaction里调Retrofit,结果超时,事务锁表10分钟。 - 解法:事务只做数据库操作,网络请求放到事务外。
@Transaction fun placeOrder(order: Order) { // 只做DB操作 orderDao.insert(order) orderDao.updateStock(order.productId, -1) } // 网络请求单独调用 viewModel.placeOrder(order) // 事务内 api.sendOrderNotification(order) // 事务外
- 问题:在
四、效果:用户说“这次真稳了”
- 查询速度:订单列表加载从500ms→50ms(用户感知“秒开”)
- 数据一致性:订单和库存100%同步,投诉率从50%→0%
- 迁移安全:v1.0→v2.0升级,0数据丢失(用户无感知)
“之前每次发版都提心吊胆,现在Room跑得稳,我敢睡整觉了。”
—— 团队后端老哥
最后说点实在的
别等崩溃了才用Room。
我们团队现在定死:新功能必须用Room,老代码逐步重构。
从一个表开始(比如User表),别想着一步到位。
试试看:
- 在
@Entity加索引(indices = [Index(value = ["字段"])])- 每次改表结构,写Migration类
- 用
@Transaction包裹关键操作(如下单、支付)
半小时搞定,用户却能多用你1年。
