POSTGRES - 具有多个连接的高效SELECT或INSERT

时间:2014-12-17 10:31:59

标签: python sql database postgresql concurrency

tl; dr我正在尝试找出最有效的方法来选择一条记录,或者如果它已经存在,则可以将其用于多条并发连接。

情况: 我正在构建一个Postgres数据库(9.3.5,x64),其中包含与客户相关的大量信息。该数据库具有“customers”表,其中包含“id”列(SERIAL PRIMARY KEY)和“system_id”列(VARCHAR(64))。 id列用作其他表中的外键以链接到客户。 “system_id”列必须是唯一的,如果它不为空。

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    system_id VARCHAR(64),
    name VARCHAR(256));

引用customers表中id的表的示例:

CREATE TABLE tsrs (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER NOT NULL REFERENCES customers(id),
    filename VARCHAR(256) NOT NULL,
    name VARCHAR(256),
    timestamp TIMESTAMP WITHOUT TIME ZONE);

我编写了一个python脚本,它使用多处理模块通过多个连接(来自不同进程)将数据推送到数据库中。

将数据推送到数据库时,每个进程需要做的第一件事是检查具有特定system_id的客户是否在customers表中。如果是,则缓存关联的customer.id。如果它尚未在表中,则添加新行,并缓存生成的customer.id。我已经为我编写了一个SQL函数:

CREATE OR REPLACE FUNCTION get_or_insert_customer(p_system_id customers.system_id%TYPE, p_name customers.name%TYPE) RETURNS customers.id%TYPE AS $$
DECLARE
    v_id customers.id%TYPE;
BEGIN
    LOCK TABLE customers IN EXCLUSIVE MODE;
    SELECT id INTO v_id FROM customers WHERE system_id=p_system_id;
    IF v_id is NULL THEN
        INSERT INTO customers(system_id, name)
            VALUES(p_system_id,p_name)
            RETURNING id INTO v_id;
    END IF;
    RETURN v_id;
END;
$$ LANGUAGE plpgsql;

问题:表锁定是我能够防止并发进程将重复的system_ids添加到表中的唯一方法。这不是很理想,因为它有效地序列化了此时的所有处理,并且基本上将将给定数量的数据推入数据库所花费的时间增加了一倍。

我想问一下,是否有一种更有效/更优雅的方式来实现“SELECT或INSERT”机制,这种机制不会造成太大的减速?我怀疑没有,但认为这是值得问的,以防万一。

非常感谢你阅读这篇文章。任何建议都非常感谢!

1 个答案:

答案 0 :(得分:1)

[不是答案] 我设法将该函数写入纯SQL,更改顺序(避免IF潜在的竞争条件)< / p>

CREATE OR REPLACE FUNCTION get_or_insert_customer
        ( p_system_id customers.system_id%TYPE
        , p_name customers.name%TYPE
        )  RETURNS customers.id%TYPE AS $func$

    LOCK TABLE customers IN EXCLUSIVE MODE;
    INSERT INTO customers(system_id, name)
    SELECT p_system_id,p_name
     WHERE NOT EXISTS (SELECT 1 FROM customers WHERE system_id = p_system_id)
        ;

    SELECT id
        FROM customers WHERE system_id = p_system_id
        ;
$func$ LANGUAGE sql;