for
Postgresql select 语句不够用吗, select for share ,select for udpate 这样的语句我从来没有用过,是不是多余。
回答是:NO
曾经还听到一个声音,select = select for share 没差的,到底是不是这样
首先要确认一点的是,无论是 share 还是update 都是因为在业务中的逻辑造成你要访问的数据需要保护,所以如果你还没有用过,可能你的业务比较简单,或者你的业务并不简单,只是你没有考虑某些问题,而从未使用过他们。
假设我们下面的图中的一个业务,下面是一个拍卖二手车的场景,在各地的拍卖都在买同一个二手车,人们在竞价,而这时有时间限制的,所以每个人读到的最近一次的购买人出的价格,并在这个价格上,提价。而这里面就会牵扯一个问题,如果我要在价格上更改,那这时的价格被我独占,否则我无法在得到一个价格后,更改的时候保证这个价格不变,然后在这个价格上我在加钱。
上面的业务场景,就设计到下面的这段截图的操作
1 首先他必须是一个事务
2 在我查询的时候我必须不能让人在更改价格
3 我要迅速的更新这个价格
4 然后释放,让别人继续出价
想想如果没有for update 这个语句,那在程序上要花费多大的力气来满足这个“事务”。
for update 将这一行数据上了锁,一个别人都不能对这行数据更改的锁,事情就到此为止了,NO NO NO
如果这时候多个人都要加 for update 锁会怎么样,那一定只能有一个人,加上这个锁。
session 1
session 2
从上图我们可以很清晰的看出,session2 无法将select for update 语句执行,一直在等待,他等待session 1 释放那个锁。session 2 只能等到 session 1 commit 后才能获得那个锁,进行更改,而那时数据一个改变,他读取的数据必然是session 1 已经更改的数据,周而复始。
我们可以把上面的过程叫做,安全更新。
那这for update 产生了什么锁我们的看一下
可以很清晰的看到 for update 加了 exclusivelock 排他锁
那问题来了,如果很多人都在竞争这行数据,那应用程序那边怎么办,都在等待,如果这边设计有问题,并没有超时设计,那其他的应用不就都卡死了。
那我们就的拿出for update NOWAIT 这条语句
session 1
session 2
从上图可以很清楚的看到,使用了for update nowait 则马上因为已经对那行数据上锁了,所以其他session 在去锁,就会报错,并提示,无法获得lock。
巧妙的使用这两句话,可以减少很多在应用程序中需要考虑的事情,简简单单就将事情解决了。
那么问题还没有完,如果两个事物锁定的记录部分重合,部分不重合,那我应该怎么办,业务逻辑中,例如抢注商标,在你确认的一刻,你有两个选择,你选择的这一批商标里面,有一部分已经被抢注了,一部分有,你此时是要不都注册,要不就都不注册,实际上此时你希望还有第三种选择,就是那个商标没有注册,你就马上注册上,其他就随它去。
那这时事情怎么办?shiyong skip locked 就可以满足上面的需求。
所以一个 for update 的用法和对应的业务逻辑都不同,适当的选择不同的方法可以事半功倍。
那说了这么半天,for share 到底又有几个意思。
相对于 for update for share 用法比较单纯,一句话,有我你别改,但都可以读。
最后一定会有一个问题,for share 和 for update 之间的最大的区别在哪里,下边这张图给出的答案。
附:上面使用的查询锁的view 代码
CREATE VIEW lockview AS
SELECT pid, virtualtransaction AS vxid, locktype AS lock_type,
mode AS lock_mode, granted,
CASE
WHEN virtualxid IS NOT NULL AND transactionid IS NOT NULL
THEN virtualxid || ' ' || transactionid
WHEN virtualxid::text IS NOT NULL
THEN virtualxid
ELSE transactionid::text END AS xid_lock, relname,
page, tuple, classid, objid, objsubid
FROM pg_locks LEFT OUTER JOIN pg_class ON (pg_locks.relation = pg_class.oid)
WHERE -- do not show our view’s locks
pid != pg_backend_pid();