3

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

这是用法:

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

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

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

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

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

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

选择速度和删除速度之间是否存在必要的权衡?

分区是唯一的解决方案吗?对此类表进行分区的好策略是什么?

我正在使用 PostgreSQL 8.4 数据库。

4

5 回答 5

4

您有没有研究过PostgreSQL 分区,而不是将其保留为单个物理表?从 8.1 版开始支持它。

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

从手册:

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

  • 对于某些类型的查询,查询性能可以显着提高。
  • 更新性能也可以提高,因为表的每个部分的索引都小于整个数据集的索引。当索引不再适合内存时,索引上的读取和写入操作都会占用更多的磁盘访问权限。
  • 如果在分区设计中计划了该要求,则可以通过简单地删除其中一个分区来完成批量删除。DROP TABLE 比批量 DELETE 快得多,更不用说随之而来的 VACUUM 开销。
  • 很少使用的数据可以迁移到更便宜、更慢的存储介质上。

这些好处通常只有在表格非常大的情况下才值得。表从分区中受益的确切点取决于应用程序,但经验法则是表的大小应该超过数据库服务器的物理内存。

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

于 2010-03-04T19:28:15.497 回答
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');
                                         查询计划                                          
-------------------------------------------------- ------------------------------------------
 结果(成本=0.00..69.20 行=2 宽度=36)
   -> 追加(成本=0.00..69.20 行=2 宽度=36)
         -> 对表名进行 Seq 扫描(成本=0.00..34.60 行=1 宽度=36)
               过滤器: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text))
         -> 对 tablename_partition_3 表名进行 Seq 扫描(成本=0.00..34.60 行=1 宽度=36)
               过滤器: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text))
(6 行)

您需要添加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)
于 2010-03-05T15:51:38.410 回答
0

如果您将此表分解为适当的分区,您将能够使用 truncate 而不是 delete,这将降低您的维护成本,因为它不会产生死空间。

于 2010-03-04T19:47:59.837 回答
0

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

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

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

于 2010-03-04T19:28:28.930 回答
0

我不是专家,但似乎在“a”列上进行分区会加快您的选择,但在日期上进行分区(正如所有其他答案所暗示的那样)会加快删除速度(删除表),但对您来说毫无用处选择。

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

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

于 2010-03-05T12:08:20.240 回答