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

时间:2018-08-09 22:21:11

标签: 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

1 个答案:

答案 0 :(得分:1)

您的问题是global CurrentID。这意味着所有用户共享一个值。

考虑以下流程:

  • 用户1开始报价,并且ID升至23。
  • 您在等待用户1的价格时,用户2开始报价,并且ID升至24。
  • 在等待用户1的价格和用户2的价格时,其中一个响应,然后您更新商品24的价格。
  • 然后另一个人做出响应,然后您再次更新商品24的价格。

因此,第24项的价格以最后一位用户设定的价格为准,与此同时,第23项的价格永远不会更新,因此其价格可能仍为NULL(或您的默认值)。


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

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离开指定的列,并在INSERT之后获得lastrowid属性:

ItemID = cursor.lastrowid

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