PostgreSQL多层分区

时间:2015-11-19 13:56:00

标签: database postgresql partitioning multi-level

我一直在使用postgreSQL数据库进行分区。我的数据库已经增长了很多,并且在分区方面做得非常好。不幸的是,我现在似乎已经在速度上遇到了另一个障碍,我正试图找出一些方法来加快数据库的速度。

我的基本设置如下: 我有一个名为database_data的主表,所有分区都从该表继承。我选择每个月有一个分区,并将其命名为:database_data_YYYY_MM,效果很好。

通过分析我的数据使用情况,我注意到,我主要是在表上插入操作,只进行一些更新。然而,更新也只出现在某种行上:我有一个名为channel_id的列(另一个表的FK)。我更新的行总是有一组可能有50个ID的channel_id,所以这是区分永不更新的行和可能的行的好方法。

我认为如果我将分区用于每月只有一个仅插入数据表和一个可能更新的数据表,它会进一步加快我的设置速度,如同我的更新每次都要检查更少的行。

我当然可以使用我现在使用的“简单”分区,并为每个月添加另一个名为database_data_YYYY_MM_update的表,并为其添加特殊约束和database_data_YYYY_MM表以便查询规划者区分表格。

但是,我想,我确实有时会对某个月的所有数据进行操作,无论是否可更新。在这种情况下,我可以加入两个表,但可以有一种更简单的方法进行此类查询。

现在回答我真正的问题:

PostgreSQL中是否可以进行“双层”分区?我的意思是,不是每个月从主表继承两个表,而是每月只有一个表直接从主表继承,例如database_data_YYYY_MM然后还有两个表继承自该表,一个表用于仅插入数据,例如database_data_YYYY_MM_insert和一个可更新数据,例如database_data_YYYY_MM_update

这会加快查询计划吗?我猜想如果中间表被淘汰,查询规划器可以立即消除两个表会更快。

这里的明显优势是我可以通过简单地使用表database_data_YYYY_MM来操作一个月的所有数据,并且我的更新可以直接使用子表。

我没想到的任何缺点?

感谢您的想法。

编辑1:

我不认为架构是回答我的问题所必需的,但如果它有助于理解我会提供一个示例架构:

CREATE TABLE database_data (
    id bigint PRIMARY KEY,
    channel_id bigint,    -- This is a FK to another table
    timestamp TIMESTAMP WITH TIME ZONE,
    value DOUBLE PRECISION
)

我在database_data表上有一个触发器,可以按需生成分区:

CREATE OR REPLACE FUNCTION function_insert_database_data() RETURNS TRIGGER AS $BODY$
DECLARE
    thistablename TEXT;
    thisyear INTEGER;
    thismonth INTEGER;
    nextmonth INTEGER;
    nextyear INTEGER;
BEGIN
    -- determine year and month of timestamp
    thismonth = extract(month from NEW.timestamp AT TIME ZONE 'UTC');
    thisyear = extract(year from NEW.timestamp AT TIME ZONE 'UTC');

    -- determine next month for timespan in check constraint
    nextyear = thisyear;
    nextmonth = thismonth + 1;
    if (nextmonth >= 13) THEN
        nextmonth = nextmonth - 12;
        nextyear = nextyear +1;
    END IF;

    -- Assemble the tablename

    thistablename = 'database_datanew_' || thisyear || '_' || thismonth;

    -- We are looping until it's successfull to catch the case when another connection simultaneously creates the table
    -- if that would be the case, we can retry inserting the data
    LOOP
        -- try to insert into table
        BEGIN
            EXECUTE 'INSERT INTO ' || quote_ident(thistablename) || ' SELECT ($1).*' USING NEW;
            -- Return NEW inserts the data into the main table allowing insert statements to return the values like "INSERT INTO ... RETURNING *"
            -- This requires us to use another trigger to delete the data again afterwards
            RETURN NEW;
        -- If the table does not exist, create it
        EXCEPTION
            WHEN UNDEFINED_TABLE THEN
                BEGIN
                    -- Create table with check constraint on timestamp
                    EXECUTE 'CREATE TABLE ' || thistablename || ' (CHECK ( timestamp >= TIMESTAMP WITH TIME ZONE '''|| thisyear || '-'|| thismonth ||'-01 00:00:00+00''
                        AND timestamp < TIMESTAMP WITH TIME ZONE '''|| nextyear || '-'|| nextmonth ||'-01 00:00:00+00'' ), PRIMARY KEY (id)
                        ) INHERITS (database_data)';
                    -- Add any trigger and indices to the table you might need
                    -- Insert the new data into the new table
                    EXECUTE 'INSERT INTO ' || quote_ident(thistablename) || ' SELECT ($1).*' USING NEW;
                    RETURN NEW;
                EXCEPTION WHEN DUPLICATE_TABLE THEN
                    -- another thread seems to have created the table already. Simply loop again.
                END;
            -- Don't insert anything on other errors
            WHEN OTHERS THEN
                RETURN NULL;
        END;
    END LOOP;
END;
$BODY$
LANGUAGE plpgsql;

CREATE TRIGGER trigger_insert_database_data
BEFORE INSERT ON database_data
FOR EACH ROW EXECUTE PROCEDURE function_insert_database_data();

对于样本数据:我们假设我们只有两个通道:1和2. 1是仅插入数据,2是可更新的。

我的两层方法是:

主表:

CREATE TABLE database_data (
    id bigint PRIMARY KEY,
    channel_id bigint,    -- This is a FK to another table
    timestamp TIMESTAMP WITH TIME ZONE,
    value DOUBLE PRECISION
)

中级表:

CREATE TABLE database_data_2015_11 (
    (CHECK ( timestamp >= TIMESTAMP WITH TIME ZONE '2015-11-01 00:00:00+00' AND timestamp < TIMESTAMP WITH TIME ZONE '2015-12-01 00:00:00+00)),
    PRIMARY KEY (id)
) INHERITS(database_data);

分区:

CREATE TABLE database_data_2015_11_insert (
    (CHECK (channel_id = 1)),
    PRIMARY KEY (id)
) INHERITS(database_data_2015_11);

CREATE TABLE database_data_2015_11_update (
    (CHECK (channel_id = 2)),
    PRIMARY KEY (id)
) INHERITS(database_data_2015_11);

当然,我需要在中间表上另一个触发器来按需创建子表。

1 个答案:

答案 0 :(得分:0)

这是一个聪明的主意,但遗憾的是它似乎没有用。如果我有一个包含1000个直接子项的父表,并且我运行的SELECT应该只从一个孩子那里获得,那么explain analyze给我的计划时间大约为16毫秒。另一方面,如果我只有10个直接孩子,并且他们都有10个孩子,并且那些都有10个孩子,我得到的查询计划时间约为29ms。我很惊讶 - 我真的觉得它会起作用!

以下是我用来生成表格的一些ruby代码:

0.upto(999) do |i|
  if i % 100 == 0
    min_group_id = i
    max_group_id = min_group_id + 100
    puts "CREATE TABLE datapoints_#{i}c (check (group_id > #{min_group_id} and group_id <= #{max_group_id})) inherits (datapoints);"
  end
  if i % 10 == 0
    min_group_id = i
    max_group_id = min_group_id + 10
    puts "CREATE TABLE datapoints_#{i}x (check (group_id > #{min_group_id} and group_id <= #{max_group_id})) inherits (datapoints_#{i / 100 * 100}c);"
  end
  puts "CREATE TABLE datapoints_#{i + 1} (check (group_id = #{i + 1})) inherits (datapoints_#{i / 10 * 10}x);"
end