PostgreSQL:如何防止SQL存储过程并发访问? (包括SELECT FOR UPDATE)

时间:2015-09-04 13:39:58

标签: postgresql stored-procedures locking database-performance

我在执行某些负载平衡算法的存储过程时遇到问题。该服务远程调用此过程,并且该过程必须使用正确的端口来回复该消息。

问题是我想要一个出色的表现(500个大写或者消息/秒)。因此,该服务每秒要求500次数据库,这是将消息转发给它的好端口。

N.B。 :我确信运行数据库服务器的VM有足够的资源来达到500个请求/秒。事实上,我为了更简单的查询而达到了这个数字(选择,更新......)。

这是存储过程:

 -- Function: loadbalancing(integer)

 -- DROP FUNCTION loadbalancing(integer);

 CREATE OR REPLACE FUNCTION loadbalancing(num integer)   RETURNS
 integer AS $BODY$DECLARE
 -- Declarations 
 port_selected integer; 
 total_call_attempts integer;

 BEGIN
 --
 -- ***   ***   ***   ***   ***   ***
 -- *** Load Balancing Algorithm  ***
 -- ***   ***   ***   ***   ***   ***
 --
 -- ***************
 -- *** locking ***
 -- ***************
 perform number_of_call_attempt, destination_rate FROM load_distribution WHERE loadb_id = num FOR UPDATE;

 -- *****************************
 -- *** select the right port ***
 -- ***************************** 
select port_number into port_selected from  ( select *, row_number()          over(order by destination_ratio asc,
 destination_target_rate desc, port_number asc) as rn from
 load_distribution where active = true and loadb_id = num ) t where
 t.rn = 1;

 -- ******************************************************
 -- *** Update the number of call attempts at one port ***
 -- ****************************************************** 
 UPDATE load_distribution SET number_of_call_attempt = number_of_call_attempt
 + 1 WHERE port_number = port_selected AND loadb_id = num;

 -- **************************************
 -- *** Get the total of call attempts ***
 -- **************************************
 SELECT SUM(number_of_call_attempt) into total_call_attempts FROM
 load_distribution WHERE loadb_id = num;

 -- *******************************
 -- *** Update destination rate ***
 -- ******************************* 
UPDATE load_distribution SET destination_rate = (number_of_call_attempt / total_call_attempts)
 WHERE loadb_id = num;

 -- ********************************
 -- *** Update destination ratio ***
 -- ******************************** 
 UPDATE load_distribution SET destination_ratio = (destination_rate / destination_target_rate) WHERE
 loadb_id = num;

 -- ***********************************************
 -- *** Return a port to the requesting service ***
 -- *********************************************** 
RETURN port_selected;

 END;$BODY$   LANGUAGE plpgsql VOLATILE   COST 100; 
 ALTER FUNCTION loadbalancing(integer)   OWNER TO postgres;

我遇到的麻烦是这最多可以达到70或100个请求/秒。当我开始达到200或更多请求/秒时,Postgres数据库会发送超时异常。

您认为这个问题出于什么原因?至于我,我认为这是一个并发访问的问题。实际上,该过程是根据其他列值选择端口号。每次呼叫/请求到达时,都会更改这些列值。之后的请求必须使用更改的值(干净状态),而不是处于脏状态或未更改值的值(如果下一个请求在上一个请求更改值之前或之前出现)。

因此,我想到的一个解决方案是使用SELECT FOR UPDATE进行显式锁定。不幸的是,我在我的算法中放置的那个似乎不起作用,或者它不是好的解决方案。我是锁定和存储过程的新手。

那么,您认为解决此问题的解决方案是什么? (即提高性能以满足更多请求/ s)。

1 个答案:

答案 0 :(得分:0)

这让我觉得在RDBMS中尝试做的事情非常糟糕。我使用易失性存储,可能是非事务性的。在这里做的最好的事情几乎肯定是使用现有的,完善的负载平衡工具,或者如果你必须DIY,那么使用专为小原子数据的低延迟快速查询而设计的技术

在多个查询中,在pl / pgsql存储过程中执行多个窗口和聚合传递是最糟糕的方法。特别是因为你基本上是对行进行序列化访问。

您可能不需要这种分配准确,无差错并且完全尊重分配的比率。相反,你可能需要它非常快,而且大致足够好。 PL / PgSQL不是这项工作的合适工具,只是几乎没有任何一点建议你改进它。它不会起作用。

除了锁定问题之外,您还会通过此快速流失更新过程生成大量 MVCC膨胀,这反过来会产生更多负载进行真空清理。所有ACID都保证您既不需要也不想要。

如果你真的必须使用这种方法,一些事情会有所帮助:

  • SET synchronous_commit = off用于调用proc
  • 的事务
  • 使用UNLOGGED表作为端口映射表
  • 一个 destination_ratio声明中设置destination_ratenumber_of_call_attemptUPDATE
  • 使用ORDER BY ... LIMIT 1获取排序中所需的端口,而不是row_number()

......但实际上,整个方法注定要失败。它正在锤击存储,为UPDATE进行行锁定和写入写入,并且每次都进行聚合传递。即使它可能全部都在shared_buffers,但也存在大量的流失。在{30}左右使用UNLOGGED表可能会有所帮助,并结合异步提交。一点点。它仍然只是很慢。

不要重新发明这个轮子。使用现有的,完善的负载平衡系统。你需要能够将统计数据保存在内存中,懒惰地更新,并使用诸如读取 - 复制 - 更新,CPU原子等技巧,以允许对共享数据结构进行非常高的并发访问。一些不会试图安全或持久的东西,并且不会尝试完全公平地分配。