假如某条记录是事务提交的前提条件,那么一般做法就是显式加锁(其他数据库)或者加 For Share 或者 For Update (postgresql).
在某些情况下,例如用户注册时,可以通过对用户名加唯一索引,来防止用户名重复,并通过 On Conflict Do Nothing Returning user_id,并且判断返回值是否为 null 来判断是否插入成功,进而给用户相应的反馈。
但是,在某些情况下,这种方法没用。例如:假如 user1 和 user2 之间不存在好友关系,那么,user1 可以向 user2 发送好友请求。我需要先确保(锁住) user1 和 user2 的好友关系不存在,然后才能插入好友请求。
mysql.innodb 可以实现,因为根据官方文档,innodb 的锁是加在索引上的。只要我在好友关系表建一个唯一索引,那么 Select For Share 将会锁住对应的索引值,尽管记录并不存在。
但是,postgres 不具备这个特性。经过测试,也不能锁住。经过仔细阅读 postgres 官方文档"Explicit Locking"节,均未发现有实现 innodb 类似功能的做法。
Advisory lock 以 1 个 int64(bigint)或者 2 个 int32(integer)作为 Identifier. 两个线程,获取同 Identifer 的锁,其中一个就会阻塞并等待另一个释放锁。而这个 Identifier 是库级的,并且由用户自己设计它的值。
比如我想锁住 user1(id=6), user(id=8)的朋友关系记录,我就要加一个 Identifier 为 01 006 008 的锁(空格是为了更好看)。006、008 是两个用户的 id,很好理解。开头的 01 是业务号,因为全库公用同一个 Identifier 空间。
这个方案很丑陋。一个 bigint 我不知道如何划分给业务号、id 号,并且未来有溢出的可能。 并且,假如有多个 id,那么会更快的溢出。 并且,如果某个键是字符串类型,那么将无从设计 Identifier.
再开一个表 FriendshipFlag,假如我想要锁住、新增、更新 Friendship (管它存在不存在) ,我只需在 FriendshipFlag 中插入(user1_id,user2_id)的记录,再进行操作,并在事务结束后删除这条记录。这相当于 advisory lock 的扩展。但是哪个神经病会这样做呢?
免谈。 冗余设计是很需要的。如果谁说业务逻辑能保证这个一致性,有种生产环境别加各种约束。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.