防止在仅插入的 postgres 中双重花费 db/sqlalchemy
Preventing double spend in insert-only postgres db/sqlalchemy
我想在一组仅插入表(带有 postgres 后端)中跟踪金融交易(以应用程序内货币表示)。我们如何定义它的一个最小示例如下:
class Balance(db.Model, CreatedTimestampMixin):
"""Balances table"""
__tablename__ = "balances"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
balance = db.Column(db.Integer, default=0)
# one-to-one each balance change has a transaction
transaction = db.relationship(
'Transaction', uselist=False, backref=db.backref('balance'))
def __repr__(self):
return f"<Balance {self.id}>"
class Transaction(db.Model, CreatedTimestampMixin):
""" Transactions that cause balance changes """
__tablename__ = "transactions"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
balance_id = db.Column(db.Integer, db.ForeignKey(
"balances.id"), nullable=False)
amount = db.Column(db.Integer, nullable=False)
def __repr__(self):
return f"<Transaction {self.id}>"
在这张图中,交易发生时,采取了以下步骤:
- 获取给定用户的最后余额
- 在余额中新建一行,余额为上次余额 + 金额(金额可以为负)
- 使用新余额和金额的外键在交易中创建一个新行。
但是,如果两个不同的交易请求同时出现,这可能会造成双花(提取旧余额,然后输入两个交易)。我也会确保旧余额 + 金额 >=0,但如果我允许双花,那么我可以允许低于 0。
如何避免双重 spend/ensure 交易被一个接一个地处理?我有点理解解决方案可以通过创建某种锁或强制执行 parent/child 排序,但不确定实现它的正确方法是什么....
做到这一点的规范方法是使用
序列化余额的读取
SELECT ... FOR NO KEY UPDATE
这会在数据库事务完成之前锁定该行并阻止其他此类语句。
请注意,如果您希望该方法有效,则必须保持数据库事务简短。另一种方法是使用“乐观锁定”。
我想在一组仅插入表(带有 postgres 后端)中跟踪金融交易(以应用程序内货币表示)。我们如何定义它的一个最小示例如下:
class Balance(db.Model, CreatedTimestampMixin):
"""Balances table"""
__tablename__ = "balances"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
balance = db.Column(db.Integer, default=0)
# one-to-one each balance change has a transaction
transaction = db.relationship(
'Transaction', uselist=False, backref=db.backref('balance'))
def __repr__(self):
return f"<Balance {self.id}>"
class Transaction(db.Model, CreatedTimestampMixin):
""" Transactions that cause balance changes """
__tablename__ = "transactions"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
balance_id = db.Column(db.Integer, db.ForeignKey(
"balances.id"), nullable=False)
amount = db.Column(db.Integer, nullable=False)
def __repr__(self):
return f"<Transaction {self.id}>"
在这张图中,交易发生时,采取了以下步骤:
- 获取给定用户的最后余额
- 在余额中新建一行,余额为上次余额 + 金额(金额可以为负)
- 使用新余额和金额的外键在交易中创建一个新行。
但是,如果两个不同的交易请求同时出现,这可能会造成双花(提取旧余额,然后输入两个交易)。我也会确保旧余额 + 金额 >=0,但如果我允许双花,那么我可以允许低于 0。
如何避免双重 spend/ensure 交易被一个接一个地处理?我有点理解解决方案可以通过创建某种锁或强制执行 parent/child 排序,但不确定实现它的正确方法是什么....
做到这一点的规范方法是使用
序列化余额的读取SELECT ... FOR NO KEY UPDATE
这会在数据库事务完成之前锁定该行并阻止其他此类语句。
请注意,如果您希望该方法有效,则必须保持数据库事务简短。另一种方法是使用“乐观锁定”。