Вытянуто из (немного) более сложного сценария из реальной жизни.Я пытаюсь использовать строку для синхронизации доступа к другому (внешнему) ресурсу.В основном это работает, но я продолжаю видеть «тупик» и пытаюсь понять причину.
Это происходит по крайней мере с версиями mariadb 10.2.22 (alpine) и 10.1.38 (ubuntu).
Таблица:
CREATE TABLE dlist (
dnum INTEGER AUTO_INCREMENT PRIMARY KEY,
dname VARCHAR(64),
dlnum INTEGER,
last_update DATETIME,
CONSTRAINT UNIQUE dlist_nn (dname, dlnum));
У меня есть несколько одновременных процессов, которые вставляют / обновляют строки в таблице.В то же время у меня есть один процесс, пытающийся «украсть» строки, которые не были обновлены в последнее время.
Вставка:
INSERT INTO dlist (dname, dlnum, last_update)
VALUES (%s, %s, NOW())
ON DUPLICATE KEY
UPDATE last_update = NOW(), dnum = LAST_INSERT_ID(dnum);
Удаление:
DELETE FROM dlist
WHERE (NOW() - INTERVAL 20 SECOND) > last_update
LIMIT 1
RETURNING dnum, dname, dlnum;
Проблема в том, что я вижу довольно частые взаимоблокировки, сообщаемые как на сторонах вставки, так и на удалении.Сообщение, как сообщает mysql-python:
(1213, 'Deadlock found when trying to get lock; try restarting transaction')
Я могу обойти проблему, повторив попытку, но почему это происходит - и есть ли способ реструктурировать SQL, чтобы предотвратить это?Я не понимаю, почему одно удаление и / или удаление вызывает «тупик».
Полный исходный код для воспроизведения (укажите в качестве аргументов имя пользователя, пароль, имя базы данных):
import time
import MySQLdb
import os
import sys
user = password = dbname = None
def create_conn():
conn = MySQLdb.connect(host='localhost', user=user, passwd=password, db=dbname)
return conn
def insert_client(dname, dlnum):
conn = create_conn()
cmd = """INSERT INTO dlist (dname, dlnum, last_update)
VALUES (%s, %s, NOW())
ON DUPLICATE KEY
UPDATE last_update = NOW(), dnum = LAST_INSERT_ID(dnum);"""
while True:
with conn as cursor:
cursor.execute(cmd, (dname, dlnum))
time.sleep(2)
def delete_client():
conn = create_conn()
cmd = """DELETE FROM dlist
WHERE (NOW() - INTERVAL 20 SECOND) > last_update
LIMIT 1
RETURNING dnum, dname, dlnum;"""
while True:
with conn as cursor:
cursor.execute(cmd)
def main():
global user, password, dbname
user, password, dbname = sys.argv[1:4]
dname = 'foo'
dlnum = 1
conn = create_conn()
with conn as cursor:
cmd = "DROP TABLE IF EXISTS dlist;"
cursor.execute(cmd)
cmd = """CREATE TABLE dlist (
dnum INTEGER AUTO_INCREMENT PRIMARY KEY,
dname VARCHAR(64),
dlnum INTEGER,
last_update DATETIME,
CONSTRAINT UNIQUE dlist_nn (dname, dlnum));"""
cursor.execute(cmd)
conn.close()
nproc = 12
for _n in range(nproc):
pid = os.fork()
if pid == 0:
insert_client(dname, dlnum)
# Main process will act as deleter.
delete_client()
if __name__ == '__main__':
main()