与(xlock,rowlock)

时间:2016-05-31 21:30:41

标签: sql-server postgresql

我在sql server中有一个查询,如下所示:

DECLARE @someIncrementalField int;
set @someIncrementalField = select max(SomeField) from SomeTable with (xlock, rowlock) where field1 = 1 and field2 = 2 and field3 = 3; 

sql server表在field1,field2,field3上有唯一的键索引。

这里的想法是根据索引锁定表的一部分并选择最大值并使用max + 1插入同一个表中。

现在我正在尝试迁移到PostgreSql,我该怎么做才是postgres?我做不到

FOR UPDATE

使用聚合选择查询。如果我做

LOCK table IN ROW EXCLUSIVE MODE

会锁定整个表格或使用字段索引的表格的一部分吗?

编辑: 不要做这个序列。我在桌子上有一个单独的身份字段。

编辑2:

id  a   b(date)     c   trace
1   1   6/1/2016    1   100001
2   1   6/1/2016    1   100002
3   1   6/1/2016    1   100003
4   1   6/1/2016    1   100004
5   1   6/1/2016    2   100001
6   1   6/1/2016    2   100002
7   1   6/1/2016    3   100001
8   1   6/1/2016    3   100002

让我们说这是表,列跟踪从100000 - 999999旋转。 下一个条目是(2016年6月1日,1日),

  1. 我想锁定表格中a = 1,b = 6/1/2016,c = 1的部分。

  2. 从该部分获取跟踪列的最大值。

  3. 添加(1,6 / 1,20,1,(步骤2中最多)+ 1)

  4. 解锁。

  5. 当发生这种情况时,我希望其他插入和更新在表格的其他部分完全正常。

    我可以根据a,b和c为跟踪列创建循环序列吗?

    编辑3 :我要感谢a_horse_with_no_name解释使用触发器的方法。我从中学到了很多东西。我首先应用了你的解决方案然后我安装了调试器并尝试了其他一些东西,以下解决方案对我有用。

    我做了这样的事情来实现我的目标。我使用pldbgapi(调试器)扩展来确保它没有锁定整个表,并且没有任何重复的条目错误。

    create or replace function insert_new(IN a_in integer, IN, b_in integer, IN c_in integer)
      returns integer
    as
    $func$
    declare new_trace integer;
    begin
      --this locks the max value until the function ends and other concurrent inserts would wait here until the function holding the lock is done
      perform trace from my_table where a=a_in and b=b_in and c=c_in order by trace desc limit 1 for update;
      select trace from my_table where a=a_in and b=b_in and c=c_in into new_trace order by trace desc limit 1;
    
      SELECT CASE WHEN new_trace IS 999999 THEN 100001 ELSE new_trace + 1 END INTO new_trace;
      insert into my_table values(a, b, c, new_trace);
    
      return query select new_trace;
    end;
    $func$
    language plpgsql;
    

1 个答案:

答案 0 :(得分:1)

我不认为有一种方法可以在不锁定整个表的情况下实现这一点,因为这是防止并发插入会破坏max() + 1检索的唯一方法。

我能想到的唯一方法是使用一些使用单独表生成跟踪值的自定义生成器函数。

create table trace_generator
(
   a_value integer not null, 
   b_value date not null,
   c_value integer not null,
   trace_value integer not null default 100000, 
   primary key (a_value, b_value, c_value)
);

insert into generator (a_value, b_value, c_value)
select distinct a,b,c
from the_table;

如果需要,如果插入了(a,b,c)的新组合,则可以在基表的插入触发器中填充trace_generator

现在创建一个获取下一个跟踪值的函数:

create function next_value(p_aval integer, p_bval date, p_cval integer)
  returns integer
as
$$
  update trace_generator
     set trace_value = case 
                         when trace_value = 999999 then 100000 
                         else trace_value + 1 
                       end
  where a_value = p_aval
    and b_value = p_bval
    and c_value = p_cval
  returning trace_value;
$$
language sql;

如果要为trace_generator的新组合自动生成(a,b,c)表中的行,可以在该函数内使用insert on conflict update而不是普通更新。

这将锁定generator表中的行,但基表中没有任何内容。要插入具有跟踪值的新行,只需调用该函数即可。这将阻止尝试为以下内容生成新跟踪值的所有其他事务:

insert into the_table (a,b,c,trace)
values (1, date '2016-06-01', 1, next_value(1, date '2016-06-01', 1));

只要未提交插入,其他任何事务都不能为a,b,c的组合插入行。如果提交了插入,则任何等待(下一个)事务都将看到递增的值。如果插入回滚,则下一个事务将看到旧值。

不幸的是,您无法调用next_value()列的默认值,因为您不允许引用函数中的其他列作为默认值。但你可以将其置于触发器中以实现自动化:

create or replace function assign_trace()
  returns trigger
as
$$
begin
  new.trace_value := next_value(new.a_value, new.b_value, new.c_value);
  return new;
end;
$$
language plpgsql;

create trigger trace_trg
   before insert on the_table
   for each row execute procedure assign_trace();

然后插入可以简化为:

insert into the_table (a,b,c)
values (1, date '2016-06-01', 1);