You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

discord.py并发指令场景下如何实现指定异步函数串行执行及解决数据库竞态

Discord机器人注册指令的并发处理问题

功能需求

我开发了Discord机器人,设置了apply指令,用户输入该指令时就会触发apply函数运行。其中apply函数的大部分代码需要支持多用户同时触发时的并发执行,但函数内调用的register异步函数需要保证同一时间仅运行一个实例。
对应的示例代码如下:

@client.command()
async def apply(ctx):
    # 需要支持并发执行的代码段
    await register(ctx.author)

async def register(user):
    # 数据库访问逻辑全部封装在独立的db.py文件中
    data = await db.execute_query('SELECT Total_Reg from Data WHERE rowid = 1')
    total = data[0][0] # 取出查询结果中的当前总注册数,存入total变量
    if total >= max_reg: # 注册数达到上限时直接关闭注册通道
        print("Registrations are closed!")
        return
    # 注册未达上限时,总注册数自增1,为用户分配唯一的注册号
    await db.execute_query('UPDATE Data SET Total_Reg = Total_Reg + 1 WHERE rowid = 1')
    reg_no = total + 1
    registrations[reg_no] = user
    await close_registration() 
    """额外要求:即使对register函数做了队列或单实例执行限制,也不希望这行代码阻塞当前流程,要让close_registration()在后台运行,不卡住当前register的代码执行。这种情况是不是应该用asyncio.ensure_future(close_registration())替代原来的await调用?"""

现存的问题

apply指令是由Discord客户端在用户输入时触发的,很容易出现多用户同时执行指令的并发场景:多个register协程会同时执行查询语句,所有协程拿到完全相同的总注册数值,最终分配的注册号会出现重复。
我知道SQLite在有连接执行数据库更新或查询时会对数据库加锁,但这种锁的作用范围仅覆盖单条语句的执行过程:第一条SELECT查询执行完释放锁后,第二条查询会立刻启动,这个时机完全发生在UPDATE自增语句执行之前,根本没办法避免读到旧的注册总数。

预期的执行逻辑

我要求注册流程严格按顺序串行执行:

  • 先通过data = await db.execute_query('SELECT Total_Reg from Data WHERE rowid = 1')获取当前的总注册数
  • 检查注册数是否达到上限
  • 如果未达上限,完成当前用户的注册,再通过await db.execute_query('UPDATE Data SET Total_Reg = Total_Reg + 1 WHERE rowid = 1')将数据库内的总注册数加1
    举个实际的并发场景:两个用户同时触发注册请求,程序必须先走完第一个用户的全注册流程,再执行第二个用户的注册逻辑,两人拿到的注册号必须唯一。

待解决的疑问

  • 这个功能应该怎么实现?是否需要使用专门的队列?
  • SQLite有没有内置的原子操作特性,可以把「判断当前注册数是否小于max_reg、满足条件就将注册数加1」的逻辑整合成单条查询完成?
  • 如果使用队列或者其他方式实现register函数的单实例运行,要让await close_registration()不阻塞当前流程、在后台运行,是不是使用asyncio.ensure_future(close_registration())替代await调用就可以?

内容的提问来源于stack exchange,提问作者Shubham

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.23 05:36:06