-
Notifications
You must be signed in to change notification settings - Fork 4
Open
Description
问题描述
当前 MysqlCache 在事务场景下存在缓存一致性风险:写操作会立即清理缓存,但如果事务回滚,缓存与数据库状态就会不一致。
问题举例
// 风险场景:事务回滚导致缓存不一致
await mysqlBin.io.transaction(async (trx) => {
await User.updateById('user001', { name: '新名字' }, trx) // 缓存立即清理
// 此时其他请求可能读取到旧数据并缓存
// const user = await User.getById('user001') // 缓存了旧数据
throw new Error('业务异常') // 事务自动回滚,但缓存已被污染
})改动思路
直接在事务对象上标记 __transactionCacheMode 属性来选择缓存策略:
legacy:立即清理缓存(默认,保持现有行为)safe:延迟清理缓存(事务提交后才清理)
具体实现
MysqlCache 核心逻辑
// src/services/MysqlCache.ts
private isTransactionSafe(trx?: CoaMysql.Transaction): boolean {
return (trx as any)?.__transactionCacheMode === 'safe'
}
private async handleCacheClear(trx: CoaMysql.Transaction | undefined, ids: string[], dataList: any[]) {
if (trx && this.isTransactionSafe(trx)) {
// 安全模式:注册事务提交后清理
this.registerCacheClearOnCommit(trx, ids, dataList)
} else {
// 传统模式:立即清理
await this.deleteCache(ids, dataList)
}
}
private registerCacheClearOnCommit(trx: CoaMysql.Transaction, ids: string[], dataList: any[]) {
if (!(trx as any).__cacheClearTasks) {
(trx as any).__cacheClearTasks = []
// 劫持事务提交方法实现延迟清理缓存
// TODO: 考虑查询 Knex 是否提供更优雅的事务钩子方案
const originalCommit = trx.commit.bind(trx)
trx.commit = async () => {
const result = await originalCommit()
// 事务提交成功后清理缓存
const tasks = (trx as any).__cacheClearTasks || []
for (const task of tasks) {
await this.deleteCache(task.ids, task.dataList)
}
return result
}
}
// 添加缓存清理任务
(trx as any).__cacheClearTasks.push({ ids, dataList })
}
// 所有写操作方法都调用 handleCacheClear
async updateById(id: string, data: any, trx?: CoaMysql.Transaction) {
const dataList = await this.getCacheChangedDataList([id], data, trx)
const result = await super.updateById(id, data, trx)
if (result) await this.handleCacheClear(trx, [id], dataList)
return result
}4. 使用示例
// 安全模式(优先在有问题的场景使用)
await mysqlBin.io.transaction(async (trx) => {
// 标记事务为安全模式
Object.assign(trx, { __transactionCacheMode: 'safe' })
await User.updateById('user001', { name: '张三' }, trx)
await Order.insert({ userId: 'user001', amount: 100 }, trx)
// 事务提交后才清理缓存,保证一致性
})
// 传统模式(现有业务保持不变)
await mysqlBin.io.transaction(async (trx) => {
await User.updateById('user002', { name: '李四' }, trx)
// 缓存立即清理,保持原有行为
})实现要求:
- 确保零侵入性:现有代码无需任何修改
- 确保向后兼容性:原有
mysqlBin.io.transaction()行为不变 - 只需要在需要安全模式时手动标记
Object.assign(trx, { __transactionCacheMode: 'safe' })
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels