- Implement PhysicalFile model to manage physical file references and reference counting. - Create Policy model with associated options and group links for storage policies. - Introduce Redeem and Report models for handling redeem codes and reports. - Add Settings model for site configuration and user settings management. - Develop Share model for sharing objects with unique codes and associated metadata. - Implement SourceLink model for managing download links associated with objects. - Create StoragePack model for managing user storage packages. - Add Tag model for user-defined tags with manual and automatic types. - Implement Task model for managing background tasks with status tracking. - Develop User model with comprehensive user management features including authentication. - Introduce UserAuthn model for managing WebAuthn credentials. - Create WebDAV model for managing WebDAV accounts associated with users.
91 lines
3.1 KiB
Python
91 lines
3.1 KiB
Python
"""
|
||
乐观锁 Mixin
|
||
|
||
提供基于 SQLAlchemy version_id_col 机制的乐观锁支持。
|
||
|
||
乐观锁适用场景:
|
||
- 涉及"状态转换"的表(如:待支付 -> 已支付)
|
||
- 涉及"数值变动"的表(如:余额、库存)
|
||
|
||
不适用场景:
|
||
- 日志表、纯插入表、低价值统计表
|
||
- 能用 UPDATE table SET col = col + 1 解决的简单计数问题
|
||
|
||
使用示例:
|
||
class Order(OptimisticLockMixin, UUIDTableBaseMixin, table=True):
|
||
status: OrderStatusEnum
|
||
amount: Decimal
|
||
|
||
# save/update 时自动检查版本号
|
||
# 如果版本号不匹配(其他事务已修改),会抛出 OptimisticLockError
|
||
try:
|
||
order = await order.save(session)
|
||
except OptimisticLockError as e:
|
||
# 处理冲突:重新查询并重试,或报错给用户
|
||
l.warning(f"乐观锁冲突: {e}")
|
||
"""
|
||
from typing import ClassVar
|
||
|
||
from sqlalchemy.orm.exc import StaleDataError
|
||
|
||
|
||
class OptimisticLockError(Exception):
|
||
"""
|
||
乐观锁冲突异常
|
||
|
||
当 save/update 操作检测到版本号不匹配时抛出。
|
||
这意味着在读取和写入之间,其他事务已经修改了该记录。
|
||
|
||
Attributes:
|
||
model_class: 发生冲突的模型类名
|
||
record_id: 记录 ID(如果可用)
|
||
expected_version: 期望的版本号(如果可用)
|
||
original_error: 原始的 StaleDataError
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
message: str,
|
||
model_class: str | None = None,
|
||
record_id: str | None = None,
|
||
expected_version: int | None = None,
|
||
original_error: StaleDataError | None = None,
|
||
):
|
||
super().__init__(message)
|
||
self.model_class = model_class
|
||
self.record_id = record_id
|
||
self.expected_version = expected_version
|
||
self.original_error = original_error
|
||
|
||
|
||
class OptimisticLockMixin:
|
||
"""
|
||
乐观锁 Mixin
|
||
|
||
使用 SQLAlchemy 的 version_id_col 机制实现乐观锁。
|
||
每次 UPDATE 时自动检查并增加版本号,如果版本号不匹配(即其他事务已修改),
|
||
session.commit() 会抛出 StaleDataError,被 save/update 方法捕获并转换为 OptimisticLockError。
|
||
|
||
原理:
|
||
1. 每条记录有一个 version 字段,初始值为 0
|
||
2. 每次 UPDATE 时,SQLAlchemy 生成的 SQL 类似:
|
||
UPDATE table SET ..., version = version + 1 WHERE id = ? AND version = ?
|
||
3. 如果 WHERE 条件不匹配(version 已被其他事务修改),
|
||
UPDATE 影响 0 行,SQLAlchemy 抛出 StaleDataError
|
||
|
||
继承顺序:
|
||
OptimisticLockMixin 必须放在 TableBaseMixin/UUIDTableBaseMixin 之前:
|
||
class Order(OptimisticLockMixin, UUIDTableBaseMixin, table=True):
|
||
...
|
||
|
||
配套重试:
|
||
如果加了乐观锁,业务层需要处理 OptimisticLockError:
|
||
- 报错给用户:"数据已被修改,请刷新后重试"
|
||
- 自动重试:重新查询最新数据并再次尝试
|
||
"""
|
||
_has_optimistic_lock: ClassVar[bool] = True
|
||
"""标记此类启用了乐观锁"""
|
||
|
||
version: int = 0
|
||
"""乐观锁版本号,每次更新自动递增"""
|