悲观锁 & 乐观锁

悲观锁(Pessimistic Lock),顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

悲观锁使用

相关方法:

  1. func (m *Model) LockUpdate() *Model
  2. func (m *Model) LockShared() *Model

gdb模块的链式操作提供了两个方法帮助您在SQL语句中实现“悲观锁”。可以在查询中使用LockShared方法从而在运行语句时带一把”共享锁“。共享锁可以避免被选择的行被修改直到事务提交:

  1. db.Table("users")->Where("votes>?", 100)->LockShared()->All();

上面这个查询等价于下面这条 SQL 语句:

  1. SELECT * FROM `users` WHERE `votes` > 100 LOCK IN SHARE MODE

此外你还可以使用LockUpdate方法。该方法用于创建FOR UPDATE锁,避免选择行被其它共享锁修改或删除:

  1. db.Table("users")->Where("votes>?", 100)->LockUpdate()->All();

上面这个查询等价于下面这条 SQL 语句:

  1. SELECT * FROM `users` WHERE `votes` > 100 FOR UPDATE

FOR UPDATELOCK IN SHARE MODE 都是用于确保被选中的记录值不能被其它事务更新(上锁),两者的区别在于 LOCK IN SHARE MODE 不会阻塞其它事务读取被锁定行记录的值,而 FOR UPDATE会阻塞其他锁定性读对锁定行的读取(非锁定性读仍然可以读取这些记录,LOCK IN SHARE MODEFOR UPDATE都是锁定性读)。

这么说比较抽象,我们举个计数器的例子:在一条语句中读取一个值,然后在另一条语句中更新这个值。使用 LOCK IN SHARE MODE 的话可以允许两个事务读取相同的初始化值,所以执行两个事务之后最终计数器的值+1;而如果使用 FOR UPDATE的话,会锁定第二个事务对记录值的读取直到第一个事务执行完成,这样计数器的最终结果就是+2了。

乐观锁使用

乐观锁,大多是基于数据版本 ( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version“ 字段来实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

锁机制总结

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。