Понимание Мариадб тупик - PullRequest
0 голосов
/ 09 мая 2019

Вытянуто из (немного) более сложного сценария из реальной жизни.Я пытаюсь использовать строку для синхронизации доступа к другому (внешнему) ресурсу.В основном это работает, но я продолжаю видеть «тупик» и пытаюсь понять причину.

Это происходит по крайней мере с версиями 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()

1 Ответ

2 голосов
/ 22 мая 2019

Кажется, нет необходимости в dnum; избавиться от этого. Для PK, продвигайте УНИКАЛЬНОЕ:

PRIMARY KEY(dname, dlnum)

Это может или не может помочь с заявленной проблемой. Это не то ...

У DELETE нет индекса, поэтому он должен сканировать большую часть, может быть, всю таблицу. Решите это, добавив

INDEX(last_update)

Подробнее

Если вы не можете избавиться от dnum, то это может помочь: Измените набор индексов на

PRIMARY KEY(dname, dlnum),
INDEX(dnum),    -- This is the minimum to keep AUTO_INCREMENT happy
INDEX(last_update)

(Я не знаю, поможет ли это в дальнейшем избежать взаимоблокировок. Но я нахожу тонкие проблемы, когда таблица имеет два уникальных ключа (PK + UNIQUE).)

...