是的,你做错了什么。
看简单的例子。
第 1 节
postgres=# select * from user_reservation_table;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | f | 0 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
postgres=# \set user 1
postgres=#
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(# SELECT uservalue FROM user_reservation_table
postgres(# WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=#
会话 2 - 同时,但仅 10 毫秒后
postgres=# \set user 2
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(# SELECT uservalue FROM user_reservation_table
postgres(# WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
会话 2 挂起....... 并且正在等待某事....
回到会话 1
postgres=# commit;
COMMIT
postgres=#
又是第 2 节
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=# commit;
COMMIT
postgres=#
会话 2 不再等待,并完成它的事务。
最后的结果是什么?:
postgres=# select * from user_reservation_table order by id;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | t | 2 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
两个用户取了相同的值1,但表中只注册了用户2
====================== 编辑 =========================== =======
在这种情况下,我们可以使用 SELECT .. FOR UPDATE 并利用 postgre 在 Read Committed Isolation Level 模式下重新评估查询的方式,
请参阅文档:http ://www.postgresql.org/docs/9.2/static/transaction-iso .html
UPDATE、DELETE、SELECT FOR UPDATE 和 SELECT FOR SHARE 命令在搜索目标行方面的行为与 SELECT 相同:它们只会找到在命令开始时提交的目标行。但是,这样的目标行在找到时可能已经被另一个并发事务更新(或删除或锁定)。在这种情况下,可能的更新程序将等待第一个更新事务提交或回滚(如果它仍在进行中)。如果第一个更新程序回滚,则其效果被否定,第二个更新程序可以继续更新最初找到的行。如果第一个更新程序提交,如果第一个更新程序删除了该行,则第二个更新程序将忽略该行,否则它将尝试将其操作应用于该行的更新版本。重新评估命令的搜索条件(WHERE 子句)以查看该行的更新版本是否仍与搜索条件匹配。如果是这样,则第二个更新程序使用该行的更新版本继续其操作。在 SELECT FOR UPDATE 和 SELECT FOR SHARE 的情况下,这意味着它是锁定并返回给客户端的行的更新版本。
简而言之:
如果一个会话锁定了该行,而另一个会话试图锁定同一行,那么第二个会话将“挂起”并等待第一个会话提交或回滚。当第一个会话提交事务时,第二个会话将重新评估 WHERE 搜索条件。如果搜索条件不匹配(因为第一个事务更改了某些列),那么第二个会话将跳过该行,并处理与 WHERE 条件匹配的下一行。
注意:此行为在可重复读取隔离级别中有所不同。在这种情况下,第二个会话将抛出错误:由于并发更新而无法序列化访问,您必须重试整个事务。
我们的查询可能如下所示:
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update ;
接着:
Update .... where id = (id returned by SELECT ... FOR UPDATE)
就个人而言,我更喜欢使用普通的旧控制台客户端(psql 用于 postgree,mysql 或 SQLPlus 用于 oracle)来测试锁定方案
所以让我们在 psql 中测试我们的查询:
session1 #select * from user_reservation_table order by id;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | t | 2 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
session1 #begin;
BEGIN
session1 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
id
----
2
(1 wiersz)
session1 #update user_reservation_table set usedyesno = true
postgres-# where id = 2;
UPDATE 1
session1 #
会话 1 锁定并更新了一行 id=2
现在会话 2
session2 #begin;
BEGIN
session2 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
会话 2 在尝试锁定行 id =2 时挂起
好的,让我们提交会话 1
session1 #commit;
COMMIT
session1 #
看看第 2 节会发生什么:
postgres-# for update ;
id
----
3
(1 wiersz)
Bingo - 会话 2 跳过了行 id = 2 并选择(并锁定)了行 id = 3
所以我们的最终查询可能是:
update user_reservation_table
set usedyesno = true
where id = (
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update
) RETURNING uservalue;
一些保留 - 此示例仅用于您的测试目的,其目的是帮助了解锁定在 postgre 中是如何工作的。
事实上,这个查询将序列化对表的访问,并且不可扩展,并且在多用户环境中可能会执行不良(缓慢)。
想象一下,有 10 个会话同时尝试从该表中获取下一行 - 每个会话都将挂起并等待上一个会话提交。
所以不要在生产代码中使用这个查询。
您真的要“从表中查找并保留下一个值”吗?为什么 ?
如果是,你必须有一些序列化设备(比如这个查询,或者,可能更容易实现,锁定整个表),但这将是一个瓶颈。