python - 当两个用户同时输入时SQL会覆盖数据(Python)

标签 python mysql sql telegram

我正在创建一个 Telegram 机器人,用户可以在其中创建他们想要出售的商品的报价。当用户一一创建时,它工作得非常好。但当他们同时执行此操作时,数据就会损坏。它们在某些字段中得到 NULL 或覆盖。

我使用 SQLite,并且了解到 SQLite 可能是问题所在,因此我切换到 MySQL (mysql.connector),但仍然存在此问题。

connMembers = mysql.connector.connect(host = "localhost", user = "root", password = "qwerty", database = "testdb")
connItems = mysql.connector.connect(host = "localhost", user = "root", password = "qwerty", database = "testdb")

def create_table_members():
    cursor = connMembers.cursor()
    sql = "CREATE TABLE IF NOT EXISTS Members" \
          "(FirstName TEXT, LastName TEXT, ChatID INTEGER UNIQUE)"
    cursor.execute(sql)
    connMembers.commit()
    cursor.close()

def create_table_items():
    cursor = connItems.cursor()
    sql = "CREATE TABLE IF NOT EXISTS " \
          "Items(FirstName TEXT, LastName TEXT, ChatID INTEGER, " \
          "ItemName TEXT, ItemPrice INTEGER, ItemID INTEGER PRIMARY KEY )"
    cursor.execute(sql)
    connMembers.commit()
    cursor.close()

这是我的输入方式:

@bot.message_handler(commands=['CreateOffer'])
def handle_createoffer(message):
    msg = bot.send_message(message.from_user.id, "Enter Item Name")
    bot.register_next_step_handler(msg, get_item_name)

此步骤将我重定向到此处:

def get_item_name(message):
    global CurrentID
    CurrentID = CurrentID + 1
    ItemName = message.text
    items_add(message.from_user.first_name, message.from_user.last_name, message.from_user.id, ItemName, 0, CurrentID)
    msg = bot.send_message(message.from_user.id, 'What is your price?')
    bot.register_next_step_handler(msg, get_item_price)

然后:

def get_item_price(message):
    try:
        ItemPrice = message.text
        if ItemPrice.isdigit():
            edit_item_price(ItemPrice, CurrentID)
        else:
            msg = bot.send_message(message.from_user.id, "not a digit")
            bot.register_next_step_handler(msg, get_item_price)
    except Exception as e:
        msg = bot.send_message(message.from_user.id, "ERROR" )
        bot.register_next_step_handler(msg, get_item_price)

要更新价格,我使用以下命令:

def edit_item_price(ItemPrice, ItemID):
    cursor = connItems.cursor()
    cursor.execute("UPDATE Items SET ItemPrice = %s WHERE ItemID = %s", (ItemPrice, ItemID))
    connItems.commit()
    cursor.close()

UPD:这就是我获取 CurrentID 的方式:

CurrentID = item_id_finder()

def item_id_finder():
    cursor = connItems.cursor()
    cursor.execute("SELECT MAX(ItemID) FROM Items")
    data = cursor.fetchall()
    connItems.commit()
    cursor.close()
    if data[0][0] == None:
        return 0
    else:
        return data[0][0]

示例:

    User1 ItemName : ItemNameOne
    User2 ItemName : ItemNameTwo
    User2 ItemPrice : 40
    User1 ItemPrice : 20

    Expected output:
    User1 - ItemNameOne - 20
    User2 - ItemNameTwo - 40

    Real OutPut:
    User1 - ItemNameOne - 0
    User2 - ItemNameTwo - 20

最佳答案

你的问题是全局CurrentID。这意味着所有用户都有一个共同的值(value)。

考虑这个流程:

  • 用户 1 发起报价,ID 变为 23。
  • 当您等待用户 1 的价格时,用户 2 开始报价,ID 会变为 24。
  • 当您等待用户 1 和用户 2 的价格时,其中一个做出了响应,您就更新了商品 24 的价格。
  • 然后另一位做出回应,您再次更新商品 24 的价格。

因此,第 24 项最终会以最后一个用户设置的价格结束,同时,第 23 项根本不会更新,因此它的价格可能仍然为 NULL(或者您的默认值)。

<小时/>

因此,首先,您需要更改 get_item_price 以将 ID 作为参数,而不是使用全局:

def get_item_price(ItemID, message):
    # now use ItemID instead of CurrentID

然后你需要传入正确的值:

    bot.register_next_step_handler(msg, functools.partial(get_item_price, ItemID))

但是如何以不受同时工作的其他人干扰的方式获取 ItemID 呢?

我其实对 Telegram 一无所知。如果它通过协程工作,您可以确保在 CurrentID = CurrentID + 1 和第一个可能阻塞的调用(可能是消息发送)之间没有被中断,所以您可以这样做这个:

def get_item_name(message):
    global CurrentID
    CurrentID = CurrentID + 1
    ItemID = CurrentID
    # now use ItemID instead of CurrentID

如果,另一方面,它是通过线程工作的,你可以随时中断,那么你需要某种同步机制,比如锁:

id_lock = threading.Lock()

def get_item_name(message):
    global CurrentID
    with id_lock:
        CurrentID = CurrentID + 1
        ItemID = CurrentID
    # now use ItemID instead of CurrentID
<小时/>

正如 OP 在评论中所建议的,如果您可以更改数据库架构,那么这里有一个更好的解决方案 - 只需使 ItemID 自动递增即可。在 MySQL 中,即:

ItemID INTEGER PRIMARY KEY AUTO_INCREMENT

然后,更改您的 INSERT 语句,将 ItemID 排除在指定列之外,并获取 lastrowid INSERT 之后的属性:

ItemID = cursor.lastrowid

您仍然必须传递该 ItemID ,以便稍后的 UPDATE 语句可以使用它(或者,如果您愿意,也可以传递游标本身),但是您不再需要担心以并发安全的方式使用锁和必须在启动任何线程之前执行的初始化函数等来选择数字。

关于python - 当两个用户同时输入时SQL会覆盖数据(Python),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51776507/

相关文章:

带前缀的 MySQL 输出 *

带空格的mysql全文搜索 bool 模式

python - 无法让 Web.py 和 jQuery 计算出 AJAX GET 请求(它变成了 OPTIONS 请求)

python - 为什么在使用 strptime 时会出现此 datetime 日期错误

mysql - 项目中的数据库设计问题?

mysql - 带有可选参数的 JpaRepository

c# - 无法从 C# 选择全局临时表 (##TempTable)

python - 从 Teradata 提取几百万条记录到 Python (pandas)

python - 如何在 Python 3.x 中识别和存储格式化文本文件中的变量和数据?

Python - Windows 中的流水线子进程