了解mariadb僵局

时间:2019-05-09 20:52:57

标签: mariadb mysql-python

从(稍微)更复杂的现实场景中提取。我正在尝试使用一行来同步对另一个(外部)资源的访问。它基本上可以正常工作,但是我一直看到“僵局”,并试图了解原因。

至少在mariadb版本10.2.22(高山)和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来防止这种情况发生?我不明白为什么单个upsert和/或delete会导致“死锁”。

要复制的完整源代码(提供用户名,密码,dbname作为参数):

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 个答案:

答案 0 :(得分:2)

似乎不需要dnum;摆脱它。对于PK,请推广UNIQUE:

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)时,我会发现一些细微的问题。)