postgresql为并行事务插入规则

时间:2013-08-05 15:52:40

标签: multithreading postgresql concurrency parallel-processing locking

我们有一个多线程应用程序使用的postgreql连接池,它将一些记录永久地插入到大表中。所以,假设我们有10个数据库连接,执行相同的功能,插入记录。

麻烦的是,我们插入了10条记录,同时它应该只插入2-3条记录,如果只有事务可以看到彼此的记录(我们的函数决定不根据记录插入记录找到的最后一条记录的日期)。

我们无法为func执行期提供表锁定。 我们尝试了不同的技术,使数据库立即将我们的规则应用于新记录,尽管它们是在并行事务中创建的,但还没有成功。

所以,我会非常感谢任何帮助或想法!

更具体地说,这是代码:

schm.events ( evtime TIMESTAMP, ref_id INTEGER, param INTEGER, type INTEGER);

记录过滤规则:

BEGIN
select count(*) into nCnt
from events e
where e.ref_id = ref_id and e.param = param and e.type = type 
and e.evtime between (evtime - interval '10 seconds') and (evtime + interval '10 seconds')

if nCnt = 0 then 
  insert into schm.events values (evtime, ref_id, param, type);
end if;
END;

更新(不幸的是评论长度不够)

我已经在生产中应用了独特的索引解决方案。结果是可以接受的,但最初的目标还没有实现。 问题是,使用唯一的哈希,我无法控制具有顺序hash_codes的2条记录之间的间隔。

以下是代码:

CREATE TABLE schm.events_hash (
  hash_code bigint NOT NULL
);
CREATE UNIQUE INDEX ui_events_hash_hash_code ON its.events_hash
  USING btree (hash_code);


--generate the hash codes data by partioning(splitting) evtime in 10 sec intervals:
INSERT into schm.events_hash 
select distinct ( cast( trunc( extract(epoch from evtime) / 10 ) || cast( ref_id as TEXT) || cast( type as TEXT ) || cast( param as TEXT ) as bigint) )
from schm.events;

--and then in a concurrently executed function I insert sequentially:
begin
INSERT into schm.events_hash values ( cast( trunc( extract(epoch from evtime) / 10 ) || cast( ref_id as TEXT) || cast( type as TEXT ) || cast( param as TEXT ) as bigint) );
insert into schm.events values (evtime, ref_id, param, type);
end;

在这种情况下,如果evtime位于散列确定的间隔内,则只插入一条记录。 情况是,我们可以跳过引用不同确定间隔的记录,但彼此接近(间隔小于60秒)。

insert into schm.events values ( '2013-07-22 19:32:37', '123', '10', '20' ); --inserted, test ok, (trunc( extract(epoch from cast('2013-07-22 19:32:37' as timestamp)) / 10 ) = 137450715 )
insert into schm.events values ( '2013-07-22 19:32:39', '123', '10', '20' ); --filtered out, test ok, (trunc( extract(epoch from cast('2013-07-22 19:32:39' as timestamp)) / 10 ) = 137450715 )
insert into schm.events values ( '2013-07-22 19:32:41', '123', '10', '20' ); --inserted, test fail, (trunc( extract(epoch from cast('2013-07-22 19:32:41' as timestamp)) / 10 ) = 137450716 )

我认为必须有一种方法来修改哈希函数以实现初始目标,但还没有找到它。也许,有一些表约束表达式,由postgresql本身执行,不在事务中吗?

1 个答案:

答案 0 :(得分:2)

关于您唯一的选择是:

  • 使用带有黑客的唯一索引将20秒范围折叠为单个值;

  • 使用advisory locking来控制通信;或

  • SERIALIZABLE隔离并故意在会话之间创建相互依赖关系。不是100%确定这在您的情况下是实用的。

你真正想要的是脏读,但是PostgreSQL不支持脏读,所以你可能会被卡在那里。

您可能需要在数据库外部使用协调员来管理您的要求。

唯一索引

您可以截断您的时间戳以进行唯一性检查,将它们四舍五入到常规边界,以便它们跳入20秒的块。然后将它们添加到(chunk_time_seconds(evtime, 20), ref_id, param, type)上的唯一索引。

只有一个插入成功,其余插入将失败并显示错误。您可以将错误捕获到PL / PgSQL中的BEGIN ... EXCEPTION块中,或者最好只在应用程序中处理它。

我认为chunk_time_seconds的合理定义可能是:

CREATE OR REPLACE FUNCTION chunk_time_seconds(t timestamptz, round_seconds integer)
RETURNS bigint
AS $$
SELECT floor(extract(epoch from t) / 20) * 20;
$$ LANGUAGE sql IMMUTABLE;

咨询锁定的起点:

可以在单个bigint或一对32位整数上进行咨询锁定。你的密钥大于它,它是三个整数,所以你不能直接使用最简单的方法:

IF pg_try_advisory_lock(ref_id, param) THEN
   ... do insert ...
END IF;

然后在10秒后,在同一连接上(但不一定在同一事务中)发出pg_advisory_unlock(ref_id_param)

它不会起作用,因为你还必须对type进行过滤,并且没有pg_advisory_lock的三整数形式形式。如果您可以将paramtype转换为小标记,则可以:

IF pg_try_advisory_lock(ref_id, param << 16 + type) THEN

但除此之外你还有点腌渍。当然,您可以对值进行散列,但是在运行哈希冲突的情况下,您可能会出现错误地跳过不应跳过的插入的(小)风险。由于冲突的行不可见,因此无法触发重新检查,因此您无法使用通常只比较行的解决方案。

所以......如果你可以将密钥装入64位,并且你的应用程序可以处理在同一连接中释放锁之前保持锁定10-20秒的需要,咨询锁将对你有效并且非常适合低开销。