discord py - 我怎样才能提高我的代码速度?
discord py - how can i boost the speed of my code up?
我有一个问题。我想为我的赠品机器人做一个任务来检查赠品是否结束。所以我为此创建了一个任务,它运行多行代码并且一切正常。但是我注意到我的代码很慢。谁能帮我说说我可以改进什么以及如何加快速度?
我使用 aiomysql 连接到我的 mariadb 数据库,并使用 time.time() 检查代码速度。
抱歉,如果我做错了什么,我是这个网站的新手,如果您需要我的任何东西,请随时发表评论。 :)
我的 Discord-py 任务:
@tasks.loop(minutes=5.5)
async def end_check(self):
await self.client.wait_until_ready()
start = time.time()
mydb = await getConnection()
mycursor = await mydb.cursor()
current = datetime.now().timestamp()
current = str(current).split(".")
# get the main giveaway-data
await mycursor.execute("SELECT guild_id, channel_id, message_id, gw_req FROM guild_giveaways WHERE end_date < %s", (current[0],))
in_database = mycursor.fetchall()
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
emb = message.embeds[0].description.split("**")
creator_id = emb[7].replace("<@", "").replace(">", "").replace("!", "")
count = 0
gwrole = None
users = []
async for user in message.reactions[0].users():
if guild.get_member(int(user.id)) is None:
continue
if user.bot:
continue
# check if a user has the role/s from the database
bypass_status = False
await mycursor.execute("SELECT bypass_role_id FROM guild_role_settings WHERE guild_id = %s AND bypass_role_id IS NOT NULL", (guild.id,))
role_exist = await mycursor.fetchone()
if role_exist:
rolelist = role_exist[0].split(" ")
for role1 in rolelist:
role = guild.get_role(int(role1))
if role in user.roles:
bypass_status = True
break
if "no_nitro" in entry[3].lower():
if user.avatar_url is not None and bypass_status is False:
if "gif" in str(user.avatar_url):
continue
if user.premium_since is not None and bypass_status is False:
continue
elif "msg" in entry[3].lower():
msg = entry[3].replace("MSG: ", "")
# get the required message count to participate
await mycursor.execute("SELECT message_count FROM guild_message_count WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
data = await mycursor.fetchone()
if data:
if int(data[0]) < int(msg) and bypass_status is False:
continue
else:
if bypass_status is False:
continue
elif "voicetime" in entry[3].lower():
seconds = entry[3].replace("VOICETIME: ", "")
# get the right voice_time to participate
await mycursor.execute("SELECT voice_time FROM guild_voice_time WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
data = await mycursor.fetchone()
if data:
if int(data[0]) < int(seconds) and bypass_status is False:
continue
else:
if bypass_status is False:
continue
elif "role_id" in entry[3].lower():
roleid = entry[3].replace("ROLE_ID: ", "")
role = guild.get_role(int(roleid))
if role not in user.roles and bypass_status is False:
continue
elif "mitglied" in entry[3].lower():
reqtime = entry[3].replace("MITGLIED:", "")
if time.time() - user.joined_at.timestamp() < int(reqtime) and bypass_status is False:
continue
if int(user.id) == int(creator_id):
continue
await mycursor.execute("SELECT ignore_role_id FROM guild_role_settings WHERE guild_id = %s", (guild.id,))
find_data = await mycursor.fetchone()
if find_data:
if find_data[0] is not None and len(find_data[0]) >= 3:
rolelist = find_data[0].split(" ")
for role1 in rolelist:
role = guild.get_role(int(role1))
if role in user.roles:
continue
users.append(user)
count += 1
if int(count) < int(emb[5]):
winners = random.sample(users, k=int(count))
if count <= 0:
await mycursor.close()
mydb.close()
return await message.reply(f"`` › **Zu wenig Teilnehmer:** Ich konnte nur `{count}` Gewinner ziehen, {emb[7]}! <:AmongUs:774306215848181760>")
zuwenig = True
else:
zuwenig = False
winners = random.sample(users, k=int(emb[5]))
status = True
# check if server bot private messages are enabled
await mycursor.execute("SELECT dm_status FROM guild_misc_settings WHERE guild_id = %s", (entry[0],))
myresult = await mycursor.fetchone()
if myresult:
if myresult[0] == "False":
status = False
role_status = False
# check if the winner should receive a role
await mycursor.execute("SELECT win_role_id FROM guild_role_settings WHERE guild_id = %s", (entry[0],))
myresult = await mycursor.fetchone()
if myresult:
if myresult[0] is not None:
gwrole = guild.get_role(int(myresult[0]))
if gwrole is not None:
role_status = True
for winner in winners:
if status is True:
try:
done = discord.Embed(title="<a:COOL:805075050368598036> › **GEWINNSPIEL GEWONNEN!** <a:COOL:805075050368598036>",
description="`` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
f"<a:gift:843914342835421185> › Du hast bei dem Gewinnspiel auf **[{guild.name}]({message.jump_url})** gewonnen!\n"
f"<a:love:855117868256198767> › Ein Teammitglied wird sich **demnächst** bei dir melden.",
color=0x778beb)
done.set_image(url="https://i.imgur.com/fBsIE3R.png")
await winner.send(content="Du hast bei einem Gewinnspiel **GEWONNEN!!** <a:blobbeers:862780904112128051>", embed=done)
except discord.Forbidden:
pass
if role_status is True:
try:
await winner.add_roles(gwrole, reason="Gewinnspiel gewonnen!")
except discord.Forbidden:
pass
database_winners = " ".join([str(winner.id) for winner in winners])
winners = ", ".join([winner.mention for winner in winners])
if winners.count(",") >= 1:
winnersdesc = f"{winners} haben **{message.embeds[0].title}** gewonnen! <a:love:855117868256198767>"
else:
winnersdesc = f"{winners} hat **{message.embeds[0].title}** gewonnen! <a:love:855117868256198767>"
embed = discord.Embed(title=message.embeds[0].title,
description="`` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
"<a:trophy:867917461377404949> **__Gewinnspiel - Gewinner__**\n"
f"<:arrow2:868989719319564359> Gewinner: {winners}\n"
f"<:arrow2:868989719319564359> Erstellt von {emb[7]}\n⠀⠀",
color=0xff4d4d)
embed.set_footer(text=f"{self.client.user.name} - Bot", icon_url=str(self.client.user.avatar_url))
embed.timestamp = datetime.now()
embed.set_thumbnail(url=message.embeds[0].thumbnail.url)
if zuwenig is False:
await message.edit(content=":name_badge: **GEWINNSPIEL VORBEI!** :name_badge:", embed=embed)
else:
await message.edit(content=f"`` › **Zu wenig Teilnehmer:** Ich konnte nur `{count}` Gewinner ziehen! <:whut:848347703217487912>", embed=embed)
await mycursor.execute("INSERT INTO guild_finished_giveaways (guild_id, channel_id, message_id, winner_id) VALUES (%s, %s, %s, %s)", (entry[0], entry[1], entry[2], database_winners))
await mycursor.execute("SELECT COUNT(*) FROM guild_finished_giveaways WHERE guild_id = %s", (entry[0],))
gcount = await mycursor.fetchone()
count2 = '{:,}'.format(int(gcount[0])).replace(",", ".")
count1 = '{:,}'.format(int(count)).replace(',', '.')
await message.reply(content=f"<a:blobbeers:862780904112128051> **Herzlichen Glückwunsch**, {winnersdesc}\n"
f"› Es gab `{count1}` **gültige** Teilnehmer. Dieses Gewinnspiel war das `{count2}`. auf dem Server. <a:PETTHEPEEPO:772189322392371201>")
if status is True:
try:
done = discord.Embed(
title="<a:Info:810178313733013504> › **GEWINNSPIEL VORBEI!**",
description="`` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
f"`✅` › Das Gewinnspiel auf **[{guild.name}]({message.jump_url})** ist vorbei!\n"
f"`` › Da du das Event gestartet hast, habe ich **dich informiert.**\n\n"
f"`` › **Zahle das Gewinnspiel** selbst aus, oder kümmere dich\n"
f"`` › darum, dass es die zuständige Person erledigt.",
color=0xffa502)
done.set_image(url="https://i.imgur.com/fBsIE3R.png")
creator = guild.get_member(int(creator_id))
await creator.send(content="Ein Gewinnspiel ist **VORBEI!**", embed=done)
except discord.Forbidden:
pass
await mycursor.execute("DELETE FROM guild_giveaways WHERE guild_id = %s AND channel_id = %s AND message_id = %s", (entry[0], entry[1], entry[2]))
await mydb.commit()
mydb.close()
await mycursor.close()
print(f"EndTask: {time.time() - start}")
我看到你的循环中有很多采石场,这就是它这么慢的原因。你不能做一个大采石场吗?您的 mysql 数据库在对 python.
的数据进行排序时会更快
也许一些提示可以开始:
- 我看到你整理了非硝基帐户,但我没有在你的第一个采石场看到它。
- 我看到你select所有消息,也许只有在赠品开始后才接受消息?因此您的脚本不必遍历所有数据。
- 几乎所有的if语句(关于数据排序)都可以写在采石场的
我自己的数据库不太好,但如果我能看到你的数据库并明确知道你想从查询中得到什么(我怀疑可能的获胜者列表),也许我可以提供帮助。这个脚本现在需要多长时间?
您最重要的问题是您在 asynchronous 代码中使用了阻塞方法 -- cursor.execute()
。这是你不应该做的事情,因为这样你的程序在等待查询结果时就不能做任何其他事情。
在 async
代码中,想法是将每个长操作抽象为 awaitable
。该函数一直执行到遇到 await
关键字为止。当这种情况发生时,该函数的执行将被暂停,直到 awaitable
的结果可用——但至关重要的是,它释放了“事件循环”以同时处理其他事情。
对于您的程序,这意味着在等待数据库结果的同时,您可以做一些有用的事情,例如收听新消息。或者发送 heartbeat packets,因为如果你不这样做,Discord 将断开你的 bot,你将不得不等到你重新连接。
要避免这种阻塞,您需要使用将阻塞调用转换为异步调用的方法。其中一些是asyncio.to_thread
and asyncio.loop.run_in_executor
,后者被认为是低级的。这是前者的一个例子:
import asyncio
async def slow_insert(value):
""" Bad example, do not do this! """
# these methods are fast, so they are not an issue
mydb = getConnection()
mycursor = mydb.cursor()
# but this hangs the event loop, and prevents the program from doing anything else
result = mycursor.execute("INSERT INTO data VALUES (%s)", value)
return
async def async_insert(value):
""" Do this instead """
# these methods are fast, so they can be done directly
mydb = getConnection()
mycursor = mydb.cursor()
# then we create a function that does what we need
# note that this does not execute the blocking function, it just wraps it into another function
blocking_insert = lambda: mycursor.execute("INSERT INTO data VALUES (%s)", value)
# we schedule this function to run on a thread, and only come back here once that thread has completed
result = await asyncio.to_thread(blocking_insert)
return result
当然,理想情况下,您会使用一个允许您以 async
方式访问数据库的库,而不是像这样包装每个调用。实际上 MySQL/MariaDB 有这样一个库,叫做 aiomysql
.
None 这将使您的程序 运行 本身更快,但它会使发生的任何缓慢都不会导致 Discord 断开连接,并且它不会' 阻止您的机器人响应消息。要真正让它更快,您必须优化查询。
一般来说,更少、更复杂的查询比更多、更简单的查询要好——数据库服务器足够智能,能够优化更复杂的查询。在您的代码中,有很多像这样的简单查询:
SELECT ignore_role_id FROM guild_role_settings WHERE guild_id = %s
SELECT bypass_role_id FROM guild_role_settings WHERE guild_id = %s AND bypass_role_id IS NOT NULL
SELECT dm_status FROM guild_misc_settings WHERE guild_id = %s
SELECT win_role_id FROM guild_role_settings WHERE guild_id = %s
请注意所有这些查询如何只依赖于 group_id
。这意味着它们甚至根本不必在循环内,它们可以移到循环外。也许更重要的是,您可以将它们全部连接到一个查询中:
-- SETUP
CREATE TABLE IF NOT EXISTS guild_role_settings (guild_id INTEGER, ignore_role_id INTEGER, bypass_role_id INTEGER, win_role_id INTEGER);
CREATE TABLE IF NOT EXISTS guild_misc_settings (guild_id INTEGER, dm_status BOOLEAN);
CREATE TABLE IF NOT EXISTS some_new_table (guild_id INTEGER, some_new_property INTEGER);
INSERT INTO guild_role_settings VALUES (1, 2, 3, 4);
INSERT INTO guild_role_settings VALUES (2, 0, 0, 0);
INSERT INTO guild_misc_settings VALUES (1, 1);
INSERT INTO guild_misc_settings VALUES (2, 0);
INSERT INTO some_new_table VALUES (1, 1337);
INSERT INTO some_new_table VALUES (2, 0);
-- END SETUP
SELECT
guild_role_settings.ignore_role_id,
guild_role_settings.bypass_role_id,
guild_role_settings.win_role_id,
guild_misc_settings.dm_status,
some_new_table.some_new_property
FROM
guild_misc_settings NATURAL JOIN guild_role_settings NATURAL JOIN some_new_table
WHERE guild_id=1;
Try it online! Also, if you're not clear on what NATURAL JOIN
does, take a look at the Wikipedia page with examples。简而言之,这允许您跨不同的 table 执行查询,方法是将它们视为单个 table,并按此进行查询。
对于包含会员 ID 的查询,您还应该将这两个查询合二为一,但最好通过首先获取所有成员,然后一起对所有成员执行查询,然后使用该查询的结果执行操作:
# instead of this:
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
async for user in message.reactions[0].users():
await mycursor.execute("SELECT guild_message_count.message_count, guild_voice_time.voice_time FROM guild_message_count NATURAL JOIN guild_message_count WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
msg_count, voice_time = await mycursor.fetchone()
# do something...
# do this instead:
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
user_objects = dict()
user_ids = []
async for user in message.reactions[0].users():
user_ids.append(user.id)
user_objects[user.id] = user
# now we have the list of user IDs, as well as their corresponding objects
# we will now form our query of the form
# SELECT ... WHERE user_id IN (%s, %s, %s, %s)
# to query all the user IDs at once
# the idea comes from
# note that while manipulating SQL strings is usually dangerous
# because of SQL injections, here we are only using the length
# of the list as a parameter, so it is okay.
query = "SELECT user_id, guild_message_count.message_count, guild_voice_time.voice_time FROM guild_message_count NATURAL JOIN guild_message_count WHERE guild_id = %s AND user_id IN "
parameters = ["%s" for _ in user_ids]
parameter_string = "(" + ( ", ".join(parameters) ) + ")"
query += parameter_string
await mycursor.execute(query, [guild.id] + user_ids)
# now the cursor has the resultset of (user_id, msg_count, voice_time)
async for user_id, msg_count, voice_time in mycursor:
# do something...
这样,您将在函数开始时执行大型查询,而不是在每次迭代时执行小型查询。
除了这些之外,您可能还可以进行其他优化,但到目前为止还不是很明显。即便如此,它们将是 SQL 优化而不是 Python 代码优化,因此它们可能更适合放在另一个问题中。
我有一个问题。我想为我的赠品机器人做一个任务来检查赠品是否结束。所以我为此创建了一个任务,它运行多行代码并且一切正常。但是我注意到我的代码很慢。谁能帮我说说我可以改进什么以及如何加快速度?
我使用 aiomysql 连接到我的 mariadb 数据库,并使用 time.time() 检查代码速度。
抱歉,如果我做错了什么,我是这个网站的新手,如果您需要我的任何东西,请随时发表评论。 :)
我的 Discord-py 任务:
@tasks.loop(minutes=5.5)
async def end_check(self):
await self.client.wait_until_ready()
start = time.time()
mydb = await getConnection()
mycursor = await mydb.cursor()
current = datetime.now().timestamp()
current = str(current).split(".")
# get the main giveaway-data
await mycursor.execute("SELECT guild_id, channel_id, message_id, gw_req FROM guild_giveaways WHERE end_date < %s", (current[0],))
in_database = mycursor.fetchall()
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
emb = message.embeds[0].description.split("**")
creator_id = emb[7].replace("<@", "").replace(">", "").replace("!", "")
count = 0
gwrole = None
users = []
async for user in message.reactions[0].users():
if guild.get_member(int(user.id)) is None:
continue
if user.bot:
continue
# check if a user has the role/s from the database
bypass_status = False
await mycursor.execute("SELECT bypass_role_id FROM guild_role_settings WHERE guild_id = %s AND bypass_role_id IS NOT NULL", (guild.id,))
role_exist = await mycursor.fetchone()
if role_exist:
rolelist = role_exist[0].split(" ")
for role1 in rolelist:
role = guild.get_role(int(role1))
if role in user.roles:
bypass_status = True
break
if "no_nitro" in entry[3].lower():
if user.avatar_url is not None and bypass_status is False:
if "gif" in str(user.avatar_url):
continue
if user.premium_since is not None and bypass_status is False:
continue
elif "msg" in entry[3].lower():
msg = entry[3].replace("MSG: ", "")
# get the required message count to participate
await mycursor.execute("SELECT message_count FROM guild_message_count WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
data = await mycursor.fetchone()
if data:
if int(data[0]) < int(msg) and bypass_status is False:
continue
else:
if bypass_status is False:
continue
elif "voicetime" in entry[3].lower():
seconds = entry[3].replace("VOICETIME: ", "")
# get the right voice_time to participate
await mycursor.execute("SELECT voice_time FROM guild_voice_time WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
data = await mycursor.fetchone()
if data:
if int(data[0]) < int(seconds) and bypass_status is False:
continue
else:
if bypass_status is False:
continue
elif "role_id" in entry[3].lower():
roleid = entry[3].replace("ROLE_ID: ", "")
role = guild.get_role(int(roleid))
if role not in user.roles and bypass_status is False:
continue
elif "mitglied" in entry[3].lower():
reqtime = entry[3].replace("MITGLIED:", "")
if time.time() - user.joined_at.timestamp() < int(reqtime) and bypass_status is False:
continue
if int(user.id) == int(creator_id):
continue
await mycursor.execute("SELECT ignore_role_id FROM guild_role_settings WHERE guild_id = %s", (guild.id,))
find_data = await mycursor.fetchone()
if find_data:
if find_data[0] is not None and len(find_data[0]) >= 3:
rolelist = find_data[0].split(" ")
for role1 in rolelist:
role = guild.get_role(int(role1))
if role in user.roles:
continue
users.append(user)
count += 1
if int(count) < int(emb[5]):
winners = random.sample(users, k=int(count))
if count <= 0:
await mycursor.close()
mydb.close()
return await message.reply(f"`` › **Zu wenig Teilnehmer:** Ich konnte nur `{count}` Gewinner ziehen, {emb[7]}! <:AmongUs:774306215848181760>")
zuwenig = True
else:
zuwenig = False
winners = random.sample(users, k=int(emb[5]))
status = True
# check if server bot private messages are enabled
await mycursor.execute("SELECT dm_status FROM guild_misc_settings WHERE guild_id = %s", (entry[0],))
myresult = await mycursor.fetchone()
if myresult:
if myresult[0] == "False":
status = False
role_status = False
# check if the winner should receive a role
await mycursor.execute("SELECT win_role_id FROM guild_role_settings WHERE guild_id = %s", (entry[0],))
myresult = await mycursor.fetchone()
if myresult:
if myresult[0] is not None:
gwrole = guild.get_role(int(myresult[0]))
if gwrole is not None:
role_status = True
for winner in winners:
if status is True:
try:
done = discord.Embed(title="<a:COOL:805075050368598036> › **GEWINNSPIEL GEWONNEN!** <a:COOL:805075050368598036>",
description="`` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
f"<a:gift:843914342835421185> › Du hast bei dem Gewinnspiel auf **[{guild.name}]({message.jump_url})** gewonnen!\n"
f"<a:love:855117868256198767> › Ein Teammitglied wird sich **demnächst** bei dir melden.",
color=0x778beb)
done.set_image(url="https://i.imgur.com/fBsIE3R.png")
await winner.send(content="Du hast bei einem Gewinnspiel **GEWONNEN!!** <a:blobbeers:862780904112128051>", embed=done)
except discord.Forbidden:
pass
if role_status is True:
try:
await winner.add_roles(gwrole, reason="Gewinnspiel gewonnen!")
except discord.Forbidden:
pass
database_winners = " ".join([str(winner.id) for winner in winners])
winners = ", ".join([winner.mention for winner in winners])
if winners.count(",") >= 1:
winnersdesc = f"{winners} haben **{message.embeds[0].title}** gewonnen! <a:love:855117868256198767>"
else:
winnersdesc = f"{winners} hat **{message.embeds[0].title}** gewonnen! <a:love:855117868256198767>"
embed = discord.Embed(title=message.embeds[0].title,
description="`` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
"<a:trophy:867917461377404949> **__Gewinnspiel - Gewinner__**\n"
f"<:arrow2:868989719319564359> Gewinner: {winners}\n"
f"<:arrow2:868989719319564359> Erstellt von {emb[7]}\n⠀⠀",
color=0xff4d4d)
embed.set_footer(text=f"{self.client.user.name} - Bot", icon_url=str(self.client.user.avatar_url))
embed.timestamp = datetime.now()
embed.set_thumbnail(url=message.embeds[0].thumbnail.url)
if zuwenig is False:
await message.edit(content=":name_badge: **GEWINNSPIEL VORBEI!** :name_badge:", embed=embed)
else:
await message.edit(content=f"`` › **Zu wenig Teilnehmer:** Ich konnte nur `{count}` Gewinner ziehen! <:whut:848347703217487912>", embed=embed)
await mycursor.execute("INSERT INTO guild_finished_giveaways (guild_id, channel_id, message_id, winner_id) VALUES (%s, %s, %s, %s)", (entry[0], entry[1], entry[2], database_winners))
await mycursor.execute("SELECT COUNT(*) FROM guild_finished_giveaways WHERE guild_id = %s", (entry[0],))
gcount = await mycursor.fetchone()
count2 = '{:,}'.format(int(gcount[0])).replace(",", ".")
count1 = '{:,}'.format(int(count)).replace(',', '.')
await message.reply(content=f"<a:blobbeers:862780904112128051> **Herzlichen Glückwunsch**, {winnersdesc}\n"
f"› Es gab `{count1}` **gültige** Teilnehmer. Dieses Gewinnspiel war das `{count2}`. auf dem Server. <a:PETTHEPEEPO:772189322392371201>")
if status is True:
try:
done = discord.Embed(
title="<a:Info:810178313733013504> › **GEWINNSPIEL VORBEI!**",
description="`` › Lade den Bot **[hier](https://bl4cklist.de/invites/gift-bot)** ein.\n\n"
f"`✅` › Das Gewinnspiel auf **[{guild.name}]({message.jump_url})** ist vorbei!\n"
f"`` › Da du das Event gestartet hast, habe ich **dich informiert.**\n\n"
f"`` › **Zahle das Gewinnspiel** selbst aus, oder kümmere dich\n"
f"`` › darum, dass es die zuständige Person erledigt.",
color=0xffa502)
done.set_image(url="https://i.imgur.com/fBsIE3R.png")
creator = guild.get_member(int(creator_id))
await creator.send(content="Ein Gewinnspiel ist **VORBEI!**", embed=done)
except discord.Forbidden:
pass
await mycursor.execute("DELETE FROM guild_giveaways WHERE guild_id = %s AND channel_id = %s AND message_id = %s", (entry[0], entry[1], entry[2]))
await mydb.commit()
mydb.close()
await mycursor.close()
print(f"EndTask: {time.time() - start}")
我看到你的循环中有很多采石场,这就是它这么慢的原因。你不能做一个大采石场吗?您的 mysql 数据库在对 python.
的数据进行排序时会更快也许一些提示可以开始:
- 我看到你整理了非硝基帐户,但我没有在你的第一个采石场看到它。
- 我看到你select所有消息,也许只有在赠品开始后才接受消息?因此您的脚本不必遍历所有数据。
- 几乎所有的if语句(关于数据排序)都可以写在采石场的
我自己的数据库不太好,但如果我能看到你的数据库并明确知道你想从查询中得到什么(我怀疑可能的获胜者列表),也许我可以提供帮助。这个脚本现在需要多长时间?
您最重要的问题是您在 asynchronous 代码中使用了阻塞方法 -- cursor.execute()
。这是你不应该做的事情,因为这样你的程序在等待查询结果时就不能做任何其他事情。
在 async
代码中,想法是将每个长操作抽象为 awaitable
。该函数一直执行到遇到 await
关键字为止。当这种情况发生时,该函数的执行将被暂停,直到 awaitable
的结果可用——但至关重要的是,它释放了“事件循环”以同时处理其他事情。
对于您的程序,这意味着在等待数据库结果的同时,您可以做一些有用的事情,例如收听新消息。或者发送 heartbeat packets,因为如果你不这样做,Discord 将断开你的 bot,你将不得不等到你重新连接。
要避免这种阻塞,您需要使用将阻塞调用转换为异步调用的方法。其中一些是asyncio.to_thread
and asyncio.loop.run_in_executor
,后者被认为是低级的。这是前者的一个例子:
import asyncio
async def slow_insert(value):
""" Bad example, do not do this! """
# these methods are fast, so they are not an issue
mydb = getConnection()
mycursor = mydb.cursor()
# but this hangs the event loop, and prevents the program from doing anything else
result = mycursor.execute("INSERT INTO data VALUES (%s)", value)
return
async def async_insert(value):
""" Do this instead """
# these methods are fast, so they can be done directly
mydb = getConnection()
mycursor = mydb.cursor()
# then we create a function that does what we need
# note that this does not execute the blocking function, it just wraps it into another function
blocking_insert = lambda: mycursor.execute("INSERT INTO data VALUES (%s)", value)
# we schedule this function to run on a thread, and only come back here once that thread has completed
result = await asyncio.to_thread(blocking_insert)
return result
当然,理想情况下,您会使用一个允许您以 async
方式访问数据库的库,而不是像这样包装每个调用。实际上 MySQL/MariaDB 有这样一个库,叫做 aiomysql
.
None 这将使您的程序 运行 本身更快,但它会使发生的任何缓慢都不会导致 Discord 断开连接,并且它不会' 阻止您的机器人响应消息。要真正让它更快,您必须优化查询。
一般来说,更少、更复杂的查询比更多、更简单的查询要好——数据库服务器足够智能,能够优化更复杂的查询。在您的代码中,有很多像这样的简单查询:
SELECT ignore_role_id FROM guild_role_settings WHERE guild_id = %s
SELECT bypass_role_id FROM guild_role_settings WHERE guild_id = %s AND bypass_role_id IS NOT NULL
SELECT dm_status FROM guild_misc_settings WHERE guild_id = %s
SELECT win_role_id FROM guild_role_settings WHERE guild_id = %s
请注意所有这些查询如何只依赖于 group_id
。这意味着它们甚至根本不必在循环内,它们可以移到循环外。也许更重要的是,您可以将它们全部连接到一个查询中:
-- SETUP
CREATE TABLE IF NOT EXISTS guild_role_settings (guild_id INTEGER, ignore_role_id INTEGER, bypass_role_id INTEGER, win_role_id INTEGER);
CREATE TABLE IF NOT EXISTS guild_misc_settings (guild_id INTEGER, dm_status BOOLEAN);
CREATE TABLE IF NOT EXISTS some_new_table (guild_id INTEGER, some_new_property INTEGER);
INSERT INTO guild_role_settings VALUES (1, 2, 3, 4);
INSERT INTO guild_role_settings VALUES (2, 0, 0, 0);
INSERT INTO guild_misc_settings VALUES (1, 1);
INSERT INTO guild_misc_settings VALUES (2, 0);
INSERT INTO some_new_table VALUES (1, 1337);
INSERT INTO some_new_table VALUES (2, 0);
-- END SETUP
SELECT
guild_role_settings.ignore_role_id,
guild_role_settings.bypass_role_id,
guild_role_settings.win_role_id,
guild_misc_settings.dm_status,
some_new_table.some_new_property
FROM
guild_misc_settings NATURAL JOIN guild_role_settings NATURAL JOIN some_new_table
WHERE guild_id=1;
Try it online! Also, if you're not clear on what NATURAL JOIN
does, take a look at the Wikipedia page with examples。简而言之,这允许您跨不同的 table 执行查询,方法是将它们视为单个 table,并按此进行查询。
对于包含会员 ID 的查询,您还应该将这两个查询合二为一,但最好通过首先获取所有成员,然后一起对所有成员执行查询,然后使用该查询的结果执行操作:
# instead of this:
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
async for user in message.reactions[0].users():
await mycursor.execute("SELECT guild_message_count.message_count, guild_voice_time.voice_time FROM guild_message_count NATURAL JOIN guild_message_count WHERE guild_id = %s AND user_id = %s", (guild.id, user.id))
msg_count, voice_time = await mycursor.fetchone()
# do something...
# do this instead:
for entry in in_database:
guild = self.client.get_guild(int(entry[0]))
channel = guild.get_channel(int(entry[1]))
message = await channel.fetch_message(int(entry[2]))
user_objects = dict()
user_ids = []
async for user in message.reactions[0].users():
user_ids.append(user.id)
user_objects[user.id] = user
# now we have the list of user IDs, as well as their corresponding objects
# we will now form our query of the form
# SELECT ... WHERE user_id IN (%s, %s, %s, %s)
# to query all the user IDs at once
# the idea comes from
# note that while manipulating SQL strings is usually dangerous
# because of SQL injections, here we are only using the length
# of the list as a parameter, so it is okay.
query = "SELECT user_id, guild_message_count.message_count, guild_voice_time.voice_time FROM guild_message_count NATURAL JOIN guild_message_count WHERE guild_id = %s AND user_id IN "
parameters = ["%s" for _ in user_ids]
parameter_string = "(" + ( ", ".join(parameters) ) + ")"
query += parameter_string
await mycursor.execute(query, [guild.id] + user_ids)
# now the cursor has the resultset of (user_id, msg_count, voice_time)
async for user_id, msg_count, voice_time in mycursor:
# do something...
这样,您将在函数开始时执行大型查询,而不是在每次迭代时执行小型查询。
除了这些之外,您可能还可以进行其他优化,但到目前为止还不是很明显。即便如此,它们将是 SQL 优化而不是 Python 代码优化,因此它们可能更适合放在另一个问题中。