SQL:更新列计数器并插入新行

时间:2016-05-05 10:49:28

标签: sql transactions

我有两个表(这是我用例的一个非常简化的模型):

- TableCounter with 2 columns: idEntry, counter
- TableObject with 1 column : idEntry , seq (with the pair idEntry/seq unique)

我需要在1次交易中能够:

- increase counter for idEntry = x
- insert (x,new_counter_value) in the TableObject.

知道我不能丢失任何序列,并且它是一个高度并发的事务并且被称为很多。

您如何在声明中编写此类事务(而不是存储过程)?你会为idEntry = x?

锁定TableCounter的行吗?

到目前为止,我有这个,但我寻找更好的解决方案。

BEGIN TRANSACTION;
SELECT counter FROM TableCounter WHERE idEntry=1 FOR UPDATE;
UPDATE TableCounter SET counter=counter+1 WHERE idEntry=1;
INSERT INTO TableObject(idEntry, seq) SELECT  TableCounter.idEntry, TableCounter.counter FROM TableCounter WHERE  TableCounter.idEntry = 1;
COMMIT TRANSACTION 

谢谢

1 个答案:

答案 0 :(得分:1)

如果您接下来要做的就是更新行,那么select for update是无用的(对于任何支持select for update的DBMS都是如此)

对于Postgres,这可以使用数据修改CTE在单个语句中完成:

with updated as (
  update tablecounter 
     set counter = counter + 1
  where identry = 1
  returning identry, counter
)
insert into tableobject (identry, seq)
select identry, counter
from updated;

更新将锁定该行,这意味着任何并发插入/更新(对于相同的identry)都必须等到提交或回滚上述内容。

如果我(真的)需要一个无间隙的序列,我可以忍受这种解决方案的可扩展性问题(因为需求比性能或可伸缩性更重要)我可能会把它放到一个函数中。如下所示:

定义序列(=计数器)表

create table gapless_sequence 
(
   entity text not null primary key,
   sequence_value integer not null default 0
);

-- "create" a new sequence
insert into gapless_sequence (entity) values ('some_table');
commit;

现在创建一个声明新值的函数

create function next_value(p_entity text)
  returns integer
as
$$
  update gapless_sequence
     set sequence_value = sequence_value + 1
  where entity = p_entity
  returning sequence_value;
$$
language sql;

与上面相同:获取实体的下一个序列的事务将阻止对同一实体的所有后续函数调用,直到第一个事务被提交(或回滚)。

现在定义一个使用无间隙序列的表非常简单:

create table some_table
(
   id integer primary key default next_value('some_table'),
   some_column text
);

然后你只需:

insert into some_table (some_column) values ('foo');

并发插入some_table将等到第一个事务提交。然后update将看到提交的值并返回适当的下一个序列值。

当然,这也可以在不使用表定义中的default子句的情况下完成,但是您需要在insert语句中显式调用该函数:

insert into some_table 
  (id, some_column) 
values 
 (next_value('some_table'), 'foo');

然而,在调用函数时,没有任何东西会强迫您使用正确的实体名称。

以上所有示例均假设自动提交已启用关闭