使用动态表创建进行分区时的竞争条件

时间:2017-11-24 13:32:01

标签: postgresql plpgsql

我正在尝试使用BEFORE INSERT触发器创建动态表创建表分区,以便在使用以下解决方案时创建新表和索引:

create table mylog (
    mylog_id serial not null primary key,
    ts timestamp(0) not null default now(),
    data text not null
);

CREATE OR REPLACE FUNCTION mylog_insert() RETURNS trigger AS
$BODY$
    DECLARE
        _name text;
        _from timestamp(0);
        _to timestamp(0);
    BEGIN
        SELECT into _name 'mylog_'||replace(substring(date_trunc('day', new.ts)::text from 0 for 11), '-', '');
        IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name=_name) then
            SELECT into _from date_trunc('day', new.ts)::timestamp(0);
            SELECT into _to _from + INTERVAL '1 day';
            EXECUTE 'CREATE TABLE '||_name||' () INHERITS (mylog)';
            EXECUTE 'ALTER TABLE '||_name||' ADD CONSTRAINT ts_check CHECK (ts >= '||quote_literal(_from)||' AND ts < '||quote_literal(_to)||')';
            EXECUTE 'CREATE INDEX '||_name||'_ts_idx on '||_name||'(ts)';
        END IF;
        EXECUTE 'INSERT INTO '||_name||' (ts, data) VALUES ($1, $2)' USING
            new.ts, new.data;
        RETURN null;
    END;
$BODY$
  LANGUAGE plpgsql;

CREATE TRIGGER mylog_insert
    BEFORE INSERT
    ON mylog
    FOR EACH ROW
    EXECUTE PROCEDURE mylog_insert();

一切都按预期工作,但每天第一次触发并发INSERT语句时,其中一个未能尝试“创建已存在的表”。我怀疑这是由同时触发的触发器引起的,并且都试图创建新表,只有一个可以成功。

我可能正在使用CREATE TABLE IF NOT EXIST,但我无法检测结果,因此无法可靠地创建约束和索引。

我可以做些什么来避免这样的问题?有没有办法表明该表已经被创建到其他并发触发器这一事实?或者也许有一种方法可以知道CREATE TABLE IF NOT EXISTS是否创建了新表?

2 个答案:

答案 0 :(得分:1)

我所做的是创建一个pgAgent作业,每天运行并提前创建3个月的表。

CREATE OR REPLACE FUNCTION avl_db.create_alltables()
  RETURNS numeric AS
$BODY$
DECLARE
    rec record;
BEGIN

    FOR rec IN     
        SELECT    date_trunc('day', i::timestamp without time zone) as table_day
        FROM      generate_series(now()::date, 
                                  now()::date + '3 MONTH'::interval, 
                                  '1 DAY'::interval) as i
    LOOP 
        PERFORM avl_db.create_table (rec.table_day);
    END LOOP; 

    PERFORM avl_db.avl_partition(now()::date, 
                                 now()::date + '3 MONTH'::interval);
    RETURN 0;

END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100;
ALTER FUNCTION avl_db.create_alltables()
  OWNER TO postgres;
  • create_table与您的CREATE TABLE代码
  • 非常相似
  • avl_partition更新BEFORE INSERT TRIGGER,但我看到你用动态查询做了那部分。将不得不再次检查。

此外,我发现你正在继承,但你错过了一个非常重要的CONSTRAINT

CONSTRAINT route_sources_20170601_event_time_check CHECK (
      event_time >= '2017-06-01 00:00:00'::timestamp without time zone 
  AND event_time <  '2017-06-02 00:00:00'::timestamp without time zone
)

在搜索event_time时,这会大大改善查询,因为不必检查每个表格。

了解如何检查当月的所有表格:

enter image description here

答案 1 :(得分:0)

最终,我在CREATE TABLE块中包含了BEGIN...EXCEPTION来捕获duplicate_table异常 - 这就是诀窍,但是在cronjob中预先创建表是更好的方法。

CREATE OR REPLACE FUNCTION mylog_insert() RETURNS trigger AS
$BODY$
    DECLARE
        _name text;
        _from timestamp(0);
        _to timestamp(0);
    BEGIN
        SELECT into _name 'mylog_'||replace(substring(date_trunc('day', new.ts)::text from 0 for 11), '-', '');
        IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name=_name) then
            SELECT into _from date_trunc('day', new.ts)::timestamp(0);
            SELECT into _to _from + INTERVAL '1 day';
            BEGIN
                EXECUTE 'CREATE TABLE '||_name||' () INHERITS (mylog)';
                EXECUTE 'ALTER TABLE '||_name||' ADD CONSTRAINT ts_check CHECK (ts >= '||quote_literal(_from)||' AND ts < '||quote_literal(_to)||')';
                EXECUTE 'CREATE INDEX '||_name||'_ts_idx on '||_name||'(ts)';
            EXCEPTION WHEN duplicate_table THEN
                RAISE NOTICE 'table exists -- ignoring';
            END;
    END IF;
        EXECUTE 'INSERT INTO '||_name||' (ts, data) VALUES ($1, $2)' USING
            new.ts, new.data;
        RETURN null;
    END;
$BODY$
  LANGUAGE plpgsql;