在不存在的行上“选择更新”块

时间:2011-08-05 11:31:34

标签: sql postgresql

我们在应用程序中有一些持久数据,从服务器查询然后存储在数据库中,以便我们可以跟踪其他信息。因为我们不想查询何时在内存中使用对象,所以我们执行select for update,以便阻止其他想要获取相同数据的线程。

我不确定select for update如何处理不存在的行。如果该行不存在而另一个线程尝试在同一行上执行另一个select for update,则该线程是否会被阻塞,直到另一个事务完成或者它是否也会得到一个空的结果集?如果它只获得一个空的结果集是否有任何方法可以阻止它,例如通过立即插入缺失的行?

修改

因为有一个评论,我们可能会锁定太多,这里有一些关于我们案例中具体用法的更多细节。在缩减的伪代码中,我们的程序流程如下所示:

d = queue.fetch();
r = SELECT * FROM table WHERE key = d.key() FOR UPDATE;
if r.empty() then
  r = get_data_from_somewhere_else();

new_r = process_stuff( r );


if Data was present then
   update row to new_r
else
   insert new_r

此代码在多个线程中运行,从队列中提取的数据可能与数据库中的同一行有关(因此锁定)。但是,如果多个线程正在使用需要相同行的数据,则需要对这些线程进行顺序化(顺序无关紧要)。但是,如果该行不存在,则此顺序化将失败,因为我们没有锁定。

修改

现在我有以下解决方案,这对我来说似乎是一个丑陋的黑客。

select the data for update
if zero rows match then
  insert some dummy data   // this will block if multiple transactions try to insert
  if insertion failed then
    // somebody beat us at the race
    select the data for update

do processing

if data was changed then
   update the old or dummy data
else
   rollback the whole transaction

我既不是百分百肯定,但这实际上解决了这个问题,这个解决方案似乎也不是很好的风格。因此,如果任何人必须提供更多可用的东西,这将是伟大的。

3 个答案:

答案 0 :(得分:16)

  

我不确定如何选择更新来处理不存在的行。

没有。

如果你知道新行的独特之处,你可以做的最好的事情是使用咨询锁。 (如果需要,使用hashtext(),并使用表的oid来锁定它。)

下一个最好的事情是桌锁。

话虽如此,你的问题让你听起来比你应该更多地锁定方式。只在实际需要时锁定行,即写操作。

答案 1 :(得分:0)

查看第二次编辑中添加的代码,它看起来是正确的。

至于它看起来像黑客,有几个选项 - 基本上都是关于将数据库逻辑移动到数据库。

一个是简单地将整个选择用于更新,如果不存在则在函数中插入逻辑,而是执行select get_object(key1,key2,etc)

或者,您可以创建一个插入触发器,该触发器将忽略添加条目(如果已存在)的尝试,并且只需在执行select for update之前执行插入操作。但是,这确实有可能干扰现有的其他代码。

(如果我记得的话,我会稍后编辑并添加示例代码,当我能够检查我正在做什么时。)

答案 2 :(得分:0)

示例解决方案(我还没找到更好:/)

主题A:

BEGIN;
SELECT pg_advisory_xact_lock(42); -- database semaphore arbitrary ID
SELECT * FROM t WHERE id = 1;
DELETE FROM t WHERE id = 1;
INSERT INTO t (id, value) VALUES (1, 'thread A');
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation
COMMIT;

主题B:

BEGIN;
SELECT pg_advisory_xact_lock(42); -- database semaphore arbitrary ID
SELECT * FROM t WHERE id = 1;
DELETE FROM t WHERE id = 1;
INSERT INTO t (id, value) VALUES (1, 'thread B');
SELECT 1 FROM pg_sleep(10); -- only for race condition simulation
COMMIT;

导致事务执行顺序始终正确。