最近面试遇到一个特别有意思的问题,使用MySQL做分布式锁,在未提交读的事务隔离级级别下,会有问题吗?
这个问题先放一下,我们先看一下我们用MySQL做分布式锁一般的做法:
表定义:
status表使锁的状态: 0-未上锁 1-已上锁
上锁sql:update lock_test set status=1 where id=1 and status=0;
解锁sql: update lock_test set status=0 where id=1 and status=1;
根据返回的受影响行数判断上锁和解锁是否成功。
在默认可重复读的条件下:
事务A:
事务B:
注意事务B的执行时间,这是因为事务A还没提交的时候,事务B的上锁sql会被阻塞,当事务A提交事务之后,事务B才可以执行成功上锁sql,但是因为锁已经被事务A锁定了,这里上锁是失败的。
到这里都很好理解,但是如果是在未提交读的隔离界别下呢?
set session TRANSACTION ISOLATION level read uncommitted;
可以修改当前session的事务隔离级别为未提交读
在这种隔离界别下,B事务可以在A事务修改锁的状态之后立刻看到锁的状态变化:
事务A:
事务B:
一个这样的场景,事务A上锁之后需要解锁,当解锁sql执行完成之后,事务B就可以看到当前锁的状态是未上锁,然后去进行上锁,但是之后事务A因为某种原因需要回滚(回滚之后应该是A持有锁),这种情况岂不是事务A,B都持有这把锁,难道在读未提交的情况下不能使用MySQL做分布式锁?我们试验一下:
事务A:
事务B:
可以看到,事务B去上锁是被阻塞住的,直到事务A提交了事务,事务B才能接着执行,当事务A是回滚事务时,因为锁状态还是1,事务B的上锁语句修改条数还是0,不会导致两个事务同事持有一把锁的。
总结:
当我们注意到事务B能看到事务A修改之后的数据的时候,我们可能会下意识的感觉事务B也能修改这条数据,这种感觉时错误的,在MySQL中,能否修改一条数据和能不能看到它是没有关系的(幻读有种场景就是可以修改看不到的数据)。MySQL定义的同一条数据修改对不同的事务应该串行执行,和隔离级别其实没有太多的关系。