从(稍微)更复杂的现实场景中提取。我正在尝试使用一行来同步对另一个(外部)资源的访问。它基本上可以正常工作,但是我一直看到“僵局”,并试图了解原因。
至少在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()
答案 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)时,我会发现一些细微的问题。)