2015-11-19 51 views
2

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

我的基本设置如下: 我有一个名为database_data的主表,所有分区都从中继承。我选择每个月有一个分区,并将它们命名为:database_data_YYYY_MM,这很好地工作。

通过分析我的数据使用情况,我注意到我主要在表上插入操作,只做了一些更新。但是更新也只发生在某种行上:我有一个名为channel_id的列(一个FK到另一个表)。我更新的行总是有一个channel_id,其中可能有50个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); 

我当然然后需要anot她在中间表上的触发器按需创建子表。

+0

没有代码,模式和样本数据这得到题外话 –

回答

0

这是一个聪明的想法,但可悲的是,它似乎并没有工作。如果我有一个有1000个直接孩子的父母表,并且我运行的SELECT应该只有一个孩子,那么explain analyze给我的计划时间约为16ms。另一方面,如果我只有10个直接孩子,并且他们都有10个孩子,并且这些孩子都有10个孩子,我的查询计划时间约为29毫秒。我很惊讶---我真的认为它会工作!

这里是我用来生成我的表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