数据库锁与幂等性
今天跟同事讨论了数据库加锁和幂等性的话题,本文记录一下
乐观锁和悲观锁
在并发条件下,需要对关键数据的更新加上防并发操作,在应用里加锁是没用的,在集群环境下并不能确保不并发提交。在数据库层面加锁才是最保险的方式,常见的做法有乐观锁和悲观锁。
乐观锁
乐观锁只在update之前做检查。常见的实现方式有2种,版本字段或者时间戳字段。
比如:
1 | update users set name = 'kyfxbl', version = 2 where id = 1 and version = 1; |
如果有另外一个线程已经更新了id为1的数据,那么where中的version条件就不会满足,本条update语句就会失败,从而避免了并发更新。时间戳也是类似的思路。
悲观锁
悲观锁是在事务开始前就加锁,事务提交或回滚后才释放。一般用select for update来实现。
比如:
1 | begin |
此时如果另一个线程也执行select for update或者update语句,就会阻塞,也避免了并发更新
另外注意一点,在select for update的时候,必须明确指定主键条件,才会加行锁。否则会加表锁,这样会带来更大的开销,降低并发性,而且一般是不必要的。
利用锁的特性防止应用并发
利用乐观锁和悲观锁,都可以实现防止应用并发
1 | // 利用乐观锁示意,如果已经被锁,则update不会成功 |
幂等性
为了防止重复数据插入,还要考虑幂等性。
唯一索引
比如在业务上,A、B、C的组合是唯一的,那么就应该用这3列做成组合唯一索引,这样就可以保证重复的数据绝不会插入。
在应用层面做校验,在并发条件下也是不能保证绝对正确的,数据库层面的唯一索引才是最保险的。
软删除的情况
但是如果不允许硬删除数据,加唯一索引就会有问题。比如A、B、C,还有一列deleted,用0表示正常状态,用1表示已删除。在这种情况下,就不能给A,B,C加唯一索引了,因为如果一条数据删除了,应该允许再次插入。
今天同事想到了一个很好的方案,deleted还是用0表示正常状态,删除的情况则是deleted = id,然后把A,B,C,deleted作为组合唯一索引