我希望在多个事务可以执行数据库插入或更新时确保隔离,其中过程需要旧值。
这是一个类似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
锁定,这不会阻止两个事务继续进行。当事务尝试UPDATE
或INSERT
时,这会导致死锁。
另一种方法是先尝试插入,如果有重复的密钥则更新:
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。
当然,任何需要硬编码等待或锁定整个表的解决方案都不能满足生产环境。
答案 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')