我有一个需要优化的数据库表变得太大(几亿行),但在我进行分区之前,我想我会问一些建议。
以下是用法:
0。表包含大约10列长度,每列约20个字节。
INSERTS以每秒数百次的速度执行。
SELECT语句基于列'a'(其中a ='xxxx')每小时执行几次。
DELETE语句基于DATE列执行。 (删除超过1年的日期)通常每天一次。
关键要求是加速INSERT和SELECT语句,并且能够保留1年的历史数据,而不会在删除时锁定整个表。
我猜我必须有两个索引,一个用于列'a',另一个用于日期字段。还是可以优化两者?
在选择速度和删除速度之间是否需要进行权衡?
分区是唯一的解决方案吗?分区这样的表的好策略是什么?
我正在使用PostgreSQL 8.4数据库。
答案 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”上进行分区会加快您的选择,但在日期上进行分区(因为所有其他答案都建议)会加快删除(删除表格)但会对你的选择毫无用处。
看来,两种情况都会提高插入性能。
任何专家都在关注这个问题吗? 在两个字段上进行分区是否可行/有用?