PostgreSQL IP池

时间:2018-04-26 11:53:35

标签: sql postgresql

我有以下功能从池中获取免费IP地址:

CREATE OR REPLACE FUNCTION get_ip(inp_id CHARACTER(9)) RETURNS INET AS $$
    DECLARE ip_assigned INET;
    BEGIN
        ip_assigned := (COALESCE((SELECT ip FROM ips WHERE id = inp_id),
                                 (SELECT (a.ip + 1) AS ip
                                  FROM ips a LEFT JOIN LATERAL (SELECT * FROM ips b WHERE a.ip < b.ip ORDER BY b.ip ASC LIMIT 1) AS q ON true
                                  WHERE q.ip <> (a.ip + 1)
                                  ORDER BY ip ASC LIMIT 1)));

        IF NOT EXISTS (SELECT 1 FROM ips WHERE id = inp_id) AND NOT EXISTS (SELECT 1 FROM ips WHERE ip = ip_assigned) THEN
            INSERT INTO ips VALUES (ip_assigned, inp_id);
            RETURN ip_assigned;
        ELSEIF EXISTS (SELECT 1 FROM ips WHERE id = inp_id AND ip = ip_assigned) THEN
            RETURN ip_assigned;
        ELSE
            RETURN '0.0.0.0';
        END IF;

    END;
$$ LANGUAGE plpgsql;

它似乎有效,但我不确定在检索IP并插入表格时是否需要锁定表格。

我正在检查IP是否已经存在,以及id是否需要和IP是否有地址。 如果出现问题,则会返回0.0.0.0

inp_id是请求IP的客户端,ips表有2列; ipid,用于匹配客户端ID和IP。

1 个答案:

答案 0 :(得分:1)

你的功能似乎非常复杂,我不相信它的功能完全正常。

如果ips为空,它将无效,并且您永远不会得到低于表中最低地址的地址,因此您必须确保永远不会删除此最低地址桌子。

无论如何,对你的问题:

我想您要避免将相同的地址返回给函数的并发调用者。

为此,在UNIQUE上创建ips约束就足够了,禁止两次添加相同的IP地址。

然后你应该在INSERT期间捕获该错误,并在出现错误时重试整个操作。

这是我的功能版本。

CREATE TABLE IF NOT EXISTS ips(
   ip inet UNIQUE NOT NULL,
   id character(9) PRIMARY KEY 
);  

CREATE OR REPLACE FUNCTION get_ip(inp_id character(9)) RETURNS inet
   LANGUAGE plpgsql STRICT AS
$$DECLARE
   min_ip inet := '192.168.0.0';
   max_ip inet := '192.168.255.255';
   new_ip inet;
BEGIN
   /* loop until we find and can insert a new address */
   LOOP
      BEGIN
         /* don't do anything if the entry already exists */
         SELECT ip INTO new_ip
         FROM ips 
         WHERE id = inp_id;

         IF new_ip IS NOT NULL THEN
            RETURN new_ip;
         END IF; 

         /* see if the lowest IP address is free */
         IF NOT EXISTS (SELECT 1 FROM ips 
                        WHERE ip = min_ip)
         THEN
            /* attempt to insert the new row */
            INSERT INTO ips (ip, id) 
            VALUES (min_ip, inp_id);

            /* return if that was successful */
            RETURN min_ip;
         END IF;    
         /* else, get the lowest IP address gap in "ips" */
         SELECT ip + 1 INTO new_ip
         FROM (SELECT ip,
                      CASE WHEN lead(ip) OVER (ORDER BY ip) = ip + 1
                           THEN FALSE
                           ELSE TRUE
                      END AS followed_by_gap
               FROM ips) subq
         WHERE followed_by_gap
         ORDER BY ip
         LIMIT 1;

         /* must not exceed maximum */
         IF new_ip > max_ip THEN
            RAISE EXCEPTION 'no free IP address found';
         END IF;
         /* if the table is still empty, use the minimum */
         IF new_ip IS NULL THEN
            new_ip := min_ip;
         END IF;

         /* attempt to insert the new row */
         INSERT INTO ips (ip, id)
         VALUES (new_ip, inp_id);

         /* return if that was successful */
         RETURN new_ip;
      EXCEPTION
         WHEN unique_violation THEN
            /* retry in another loop execution */
            NULL;
      END;
   END LOOP;
END;$$;

即使你不喜欢我的方法,你也可以看到我使用循环的意思。