3

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

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

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

因此,我将非常感谢任何帮助或想法!

更具体地说,这里是代码:

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 )

我想一定有办法修改hash函数来达到最初的目标,但是还没有找到。也许,有一些表约束表达式,由 postgresql 本身在事务之外执行?

4

1 回答 1

3

您唯一的选择是:

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

  • 使用咨询锁定来控制通信;或者

  • 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. 如果你可以变成smallints,你可以paramtype

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

但除此之外,你有点麻烦。当然,您可以对这些值进行哈希处理,但是您会冒着错误地跳过在哈希冲突情况下不应跳过的插入的(小)风险。无法触发重新检查,因为冲突的行不可见,因此您不能使用仅比较行的通常解决方案。

所以......如果您可以将密钥放入 64 位,并且您的应用程序可以处理在同一连接中释放它之前将锁保持 10-20 秒的需要,那么建议锁将为您工作,并且开销非常低。

于 2013-08-05T16:22:15.683 回答