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

discord.py中tasks.loop与sqlite3配合使用出现数据库锁问题如何解决

问题根因分析
  • 你使用的SQLite是文件级数据库,同一时间仅支持一个写入操作,你当前每秒执行的定时任务中,每次单条数据更新就调用一次connection.commit(),高频的提交操作会大幅提升锁冲突概率
  • 同步数据库操作和异步Discord框架混用:全局共享的cursor、connection对象没有并发保护,Discord事件循环中的其他协程(比如用户指令触发的数据库写操作)和定时任务同时操作数据库时,会直接触发锁竞争
  • 遍历逻辑缺陷:遍历playersTime列表的同时执行删除操作,会导致索引偏移、部分数据漏处理,异常被捕获后可能未正确释放数据库连接持有的锁
  • 高频提交导致的性能损耗:每次commit都会触发磁盘IO,高流量场景下IO堆积直接放大锁冲突概率
优化方案
  • 改用异步数据库驱动:将同步的sqlite3替换为aiosqlite,适配discord.py的异步执行模型,支持协程安全的数据库操作
  • 降低提交频率:同一个游戏的所有玩家计时更新完成后再执行一次commit,避免单条更新就提交
  • 加协程锁:全局添加asyncio.Lock,保证同一时间只有一个协程执行数据库写操作,彻底避免锁竞争
  • 使用参数化查询:替代直接拼接SQL的写法,避免SQL注入风险同时提升查询执行效率
  • 优化遍历逻辑:倒序遍历玩家列表避免删除元素导致的索引偏移问题
  • 长期高流量场景建议替换为支持行级锁的关系型数据库(如MySQL、PostgreSQL),从根本上解决文件锁带来的并发限制
优化后代码示例
import asyncio
import aiosqlite
import discord
from discord.ext import tasks

# 全局协程锁,保护数据库写操作
db_lock = asyncio.Lock()
nl = "\n" # 保持你原来的换行符变量

@tasks.loop(seconds = 1)
async def secondLoop():
    async with db_lock: # 拿到锁之后再操作数据库
        async with aiosqlite.connect("你的数据库文件路径.db") as conn:
            # 先查询所有待处理的游戏
            async with conn.execute("SELECT players, joinedTimePlayers, channelId, id FROM games WHERE onPick = 0") as cursor:
                games = await cursor.fetchall()
            
            guild = bot.guilds[0]
            for game in games:
                playersTime = game[1].split()
                players = game[0].split()
                updated = False # 标记当前游戏是否有修改,避免无意义提交

                # 倒序遍历避免删除元素导致索引偏移
                for playerTimeIndex in reversed(range(len(playersTime))):
                    try:
                        current_time = int(playersTime[playerTimeIndex])
                        if current_time > 1:
                            playersTime[playerTimeIndex] = str(current_time - 1)
                            updated = True
                        elif current_time == 1:
                            # 发送提醒消息
                            channel = bot.get_channel(game[2])
                            embedToChannel =  discord.Embed(
                                description = f"Пользователь <@!{players[playerTimeIndex]}> автоматически вышел из лобби, по причине бездействия", 
                                colour = discord.Color.red()
                            )
                            await channel.send(embed = embedToChannel)
                            # 删除超时玩家
                            del players[playerTimeIndex]
                            del playersTime[playerTimeIndex]
                            updated = True
                    except Exception as e:
                        # 不要吞异常,打印日志方便排查问题
                        print(f"处理玩家超时出错: {e}")
                
                # 整个游戏的所有修改完成后,一次性提交更新
                if updated:
                    await conn.execute(
                        "UPDATE games SET joinedTimePlayers = ?, players = ? WHERE id = ?",
                        (nl.join(playersTime), nl.join(players), game[3])
                    )
            # 所有游戏处理完成后一次性提交事务
            await conn.commit()

内容的提问来源于stack exchange,提问作者Argon-cell

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 15:45:02