InnoDB锁定INSERT / UPDATE并发事务

时间:2018-01-26 00:01:00

标签: concurrency locking innodb transaction-isolation

我希望在多个事务可以执行数据库插入或更新时确保隔离,其中过程需要旧值。

这是一个类似python的伪代码中的MVP,假设默认隔离级别为:

sql('BEGIN')
rows = sql('SELECT `value` FROM table WHERE `id`=<id> FOR UPDATE')
if rows:
    old_value, = rows[0]
    process(old_value, new_value)
    sql('UPDATE table SET `value`=<new_value> WHERE `id`=<id>')
else:
    sql('INSERT INTO table (`id`, `value`) VALUES (<id>, <new_value>)')
sql('COMMIT')

这个问题是FOR UPDATE导致IS锁定,这不会阻止两个事务继续进行。当事务尝试UPDATEINSERT时,这会导致死锁。

另一种方法是先尝试插入,如果有重复的密钥则更新:

sql('BEGIN')
rows_changed = sql('INSERT IGNORE INTO table (`id`, `value`) VALUES (<id>, <new_value>)')
if rows_changed == 0:
    rows = sql('SELECT `value` FROM table WHERE `id`=<id> FOR UPDATE')
    old_value, = rows[0]
    process(old_value, new_value)
    sql('UPDATE table SET `value`=<new_value> WHERE `id`=<id>')
sql('COMMIT')

此解决方案中的问题是失败的INSERT会导致S锁定,这也不会阻止两个事务继续进行,如下所述:https://stackoverflow.com/a/31184293/710358

当然,任何需要硬编码等待或锁定整个表的解决方案都不能满足生产环境。

1 个答案:

答案 0 :(得分:0)

解决此问题的方法是使用始终发出INSERT ... ON DUPLICATE KEY UPDATE ...锁的X。由于您需要旧值,因此可以执行空白更新并按照第二个解决方案继续进行操作:

sql('BEGIN')
rows_changed = sql('INSERT INTO table (`id`, `value`) VALUES (<id>, <new_value>) ON DUPLICATE KEY UPDATE `value`=`value`')
if rows_changed == 0:
    rows = sql('SELECT `value` FROM table WHERE `id`=<id> FOR UPDATE')
    old_value, = rows[0]
    process(old_value, new_value)
    sql('UPDATE table SET `value`=<new_value> WHERE `id`=<id>')
sql('COMMIT')