最优数据库表优化方法

时间:2010-03-04 19:23:37

标签: postgresql optimization partitioning

我有一个需要优化的数据库表变得太大(几亿行),但在我进行分区之前,我想我会问一些建议。

以下是用法:

0。表包含大约10列长度,每列约20个字节。

  1. INSERTS以每秒数百次的速度执行。

  2. SELECT语句基于列'a'(其中a ='xxxx')每小时执行几次。

  3. DELETE语句基于DATE列执行。 (删除超过1年的日期)通常每天一次。

  4. 关键要求是加速INSERT和SELECT语句,并且能够保留1年的历史数据,而不会在删除时锁定整个表。

    我猜我必须有两个索引,一个用于列'a',另一个用于日期字段。还是可以优化两者?

    在选择速度和删除速度之间是否需要进行权衡?

    分区是唯一的解决方案吗?分区这样的表的好策略是什么?

    我正在使用PostgreSQL 8.4数据库。

5 个答案:

答案 0 :(得分:4)

您是否考虑过PostgreSQL partitioning而不是将其保留为单个物理表?从版本8.1开始支持它。

分区可以帮助您避免在快速INSERT与快速DELETE性能之间进行选择的问题。您始终可以按年/月对表进行分区,只需删除不再需要的分区即可。丢弃分区非常快,插入小分区也非常快。

从手册:

  
    
      

分区是指将逻辑上的一个大表拆分为       较小的物理件。分区       可以提供几个好处:

    
  
     
      
  • 查询性能可以显着提高   各种查询。
  •   
  • 更新性能也可以提高,因为每一部分   表的索引小于   整个数据集的索引将是。   当一个指数不再适合   内存,读写操作   在索引上逐步采取更多   磁盘访问。
  •   
  • 批量删除可以通过简单地删除其中一个来完成   分区,如果有要求的话   计划进入分区设计。   DROP TABLE比批量快得多   删除,更不用说随之而来的了   VACUUM开销。
  •   
  • 很少使用的数据可以迁移到更便宜和更慢的存储   媒体。
  •   
     
    
      

只有当一张桌子出现时,这些好处通常才有价值       否则会非常大。最正确       表将受益的点       从分区取决于       申请,虽然是经验法则       是表的大小应该是       超过了物理记忆       数据库服务器。

             

目前,PostgreSQL支持通过表继承进行分区。       必须将每个分区创建为       单个父表的子表。       父表本身就是正常的       空;它只是为了表示而存在       整个数据集。你应该       熟悉继承(参见章节       5.8)在尝试实现分区之前。

    
  

答案 1 :(得分:3)

正如其他人所说,分区是你的答案,但是:

我对某些hash(a)进行了分区。如果a是一个整数,那么a%256就会很好。如果是文本,则为substring(md5(a) for 2)

它将加快插入和选择。

对于删除,我会让它们更频繁地运行但是更小并且还要分区。我每小时运行一次(在XX:30)并且像这样:

delete from table_name
where date<(current_date - interval '1 year')
and
  hash(a)
  =
  (extract(doy from current_timestamp) * 24
    + extract(hour from current_timestamp))::int % 256;

编辑:我刚试过这个:

create function hash(a text) returns text as $$ select substring(md5($1) for 1) $$ language sql immutable strict;
CREATE TABLE tablename (id text, mdate date);
CREATE TABLE tablename_partition_0 ( CHECK ( hash(id) = '0' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_1 ( CHECK ( hash(id) = '1' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_2 ( CHECK ( hash(id) = '2' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_3 ( CHECK ( hash(id) = '3' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_4 ( CHECK ( hash(id) = '4' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_5 ( CHECK ( hash(id) = '5' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_6 ( CHECK ( hash(id) = '6' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_7 ( CHECK ( hash(id) = '7' ) ) INHERITS (tablename); 
CREATE TABLE tablename_partition_8 ( CHECK ( hash(id) = '8' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_9 ( CHECK ( hash(id) = '9' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_a ( CHECK ( hash(id) = 'a' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_b ( CHECK ( hash(id) = 'b' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_c ( CHECK ( hash(id) = 'c' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_d ( CHECK ( hash(id) = 'd' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_e ( CHECK ( hash(id) = 'e' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_f ( CHECK ( hash(id) = 'f' ) ) INHERITS (tablename);
analyze;
explain select * from tablename where id='bar' and hash(id)=hash('bar');
                                         QUERY PLAN                                          
---------------------------------------------------------------------------------------------
 Result  (cost=0.00..69.20 rows=2 width=36)
   ->  Append  (cost=0.00..69.20 rows=2 width=36)
         ->  Seq Scan on tablename  (cost=0.00..34.60 rows=1 width=36)
               Filter: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text))
         ->  Seq Scan on tablename_partition_3 tablename  (cost=0.00..34.60 rows=1 width=36)
               Filter: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text))
(6 rows)

您需要在查询中添加hash(id)=hash('searched_value'),否则Postgres会搜索所有表格。


编辑:您还可以使用规则系统进行自动插入以更正表格:

create rule tablename_rule_0 as
  on insert to tablename where hash(NEW.id)='0'
  do instead insert into tablename_partition_0 values (NEW.*);
create rule tablename_rule_1 as
  on insert to tablename where hash(NEW.id)='1'
  do instead insert into tablename_partition_1 values (NEW.*);
-- and so on
insert into tablename (id) values ('a');
select * from tablename_partition_0;
 id | mdate 
----+-------
 a  | 
(1 row)

答案 2 :(得分:0)

一种解决方案是根据插入日期进行分区。

即,您的应用程序(或DAO)根据组合当前日期(或者说自上次分区切片启动以来的时间)和/或“最后”分区的大小的逻辑来决定插入哪个表。或者将这样的逻辑卸载到每日脚本中,并让脚本填充一些“这是要使用的分区”以供DAO使用。

这样就可以立即删除删除“旧”行的需要(只需删除旧分区); 它还确保您的插入定期开始填充小表,除其他外,它会加快“平均”INSERT / SELECT速度(最坏的情况当然仍然很慢)

答案 3 :(得分:0)

如果要将此表分解为正确的分区,则可以使用truncate而不是delete,这样可以降低维护成本,因为它不会创建死区。

答案 4 :(得分:0)

我不是专家,但似乎在列“a”上进行分区会加快您的选择,但在日期上进行分区(因为所有其他答案都建议)会加快删除(删除表格)但会对你的选择毫无用处。

看来,两种情况都会提高插入性能。

任何专家都在关注这个问题吗? 在两个字段上进行分区是否可行/有用?